Skip to content
FunDev
FunDev
fastapi

Implementing Firebase Authentication in FastAPI

Implementing Firebase Authentication in FastAPI
9 views
6 min read
#fastapi

This post explains how to connect a frontend using Firebase Authentication to a FastAPI backend.

Overview

Many web applications use Firebase Authentication on the frontend. To verify that authentication in a backend API, however, you need the Firebase Admin SDK. This post explains how to verify Firebase ID tokens and implement protected endpoints in FastAPI.

Architecture

┌─────────────┐     Firebase ID Token     ┌─────────────┐
│  Frontend   │ ─────────────────────────▶│  FastAPI    │
│  (React)    │                           │  Backend    │
└─────────────┘                           └─────────────┘
       │                                         │
       │ signInWithEmailAndPassword              │ verify_id_token
       ▼                                         ▼
┌─────────────────────────────────────────────────────────┐
│                    Firebase Auth                        │
└─────────────────────────────────────────────────────────┘

Authentication Flow

  1. The user signs in with Firebase on the frontend
  2. Firebase issues an ID token
  3. The frontend includes the token in the Authorization: Bearer <token> header when calling the API
  4. The backend verifies the token with the Firebase Admin SDK
  5. On successful verification, extract the user information and process the request

Implementation

1. Install Dependencies

pip install firebase-admin

Add to requirements.txt:

firebase-admin==6.4.0

2. Obtain a Firebase Service-Account Key

  1. Open the Firebase Console
  2. Project settings → Service accounts → Generate new private key
  3. Save the JSON file in a secure location, for example firebase-credentials.json

Caution: never commit this file to Git. Add it to .gitignore.

3. Configure the Environment

app/config.py:

from pydantic_settings import BaseSettings, SettingsConfigDict
 
class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_file=".env")
 
    debug: bool = False
    database_url: str = ""
    firebase_credentials_path: str = "firebase-credentials.json"
 
def get_settings() -> Settings:
    return Settings()

.env:

FIREBASE_CREDENTIALS_PATH=firebase-credentials.json

4. Initialize the Firebase Admin SDK

app/firebase.py:

import firebase_admin
from firebase_admin import credentials, auth
from pathlib import Path
 
from app.config import get_settings
 
_firebase_app = None
 
def get_firebase_app():
    """Firebase Admin SDK 앱 인스턴스 반환 (싱글톤)"""
    global _firebase_app
 
    if _firebase_app is not None:
        return _firebase_app
 
    settings = get_settings()
    cred_path = Path(settings.firebase_credentials_path)
 
    if not cred_path.exists():
        raise FileNotFoundError(
            f"Firebase credentials file not found: {cred_path}"
        )
 
    cred = credentials.Certificate(str(cred_path))
    _firebase_app = firebase_admin.initialize_app(cred)
 
    return _firebase_app
 
def verify_id_token(id_token: str) -> dict:
    """
    Firebase ID Token 검증
 
    Returns:
        dict: 디코딩된 토큰 정보 (uid, email, name 등)
 
    Raises:
        firebase_admin.auth.ExpiredIdTokenError: 토큰 만료
        firebase_admin.auth.InvalidIdTokenError: 잘못된 토큰
    """
    get_firebase_app()  # 앱 초기화 보장
    decoded_token = auth.verify_id_token(id_token)
    return decoded_token

Key points:

  • Use the singleton pattern to avoid initializing the Firebase app more than once
  • verify_id_token automatically verifies the signature, expiration, audience, and other token properties

5. Implement the FastAPI Dependency

app/dependencies.py:

from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from firebase_admin import auth as firebase_auth
 
from app.firebase import verify_id_token
 
security = HTTPBearer()
 
class FirebaseUser:
    """Firebase 인증 사용자 정보"""
 
    def __init__(self, uid: str, email: str | None, name: str | None, token_data: dict):
        self.uid = uid
        self.email = email
        self.name = name
        self.token_data = token_data
 
async def get_current_user(
    credentials: HTTPAuthorizationCredentials = Depends(security),
) -> FirebaseUser:
    """
    Firebase ID Token을 검증하고 사용자 정보 반환
 
    Usage:
        @router.get("/protected")
        def protected_route(user: FirebaseUser = Depends(get_current_user)):
            return {"uid": user.uid}
    """
    token = credentials.credentials
 
    try:
        decoded_token = verify_id_token(token)
 
        return FirebaseUser(
            uid=decoded_token["uid"],
            email=decoded_token.get("email"),
            name=decoded_token.get("name"),
            token_data=decoded_token,
        )
 
    except firebase_auth.ExpiredIdTokenError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Token has expired",
            headers={"WWW-Authenticate": "Bearer"},
        )
    except firebase_auth.InvalidIdTokenError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid authentication token",
            headers={"WWW-Authenticate": "Bearer"},
        )
    except Exception as e:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail=f"Authentication failed: {str(e)}",
            headers={"WWW-Authenticate": "Bearer"},
        )

Important: pay attention to the order of exception handling!

ExpiredIdTokenError is a subclass of InvalidIdTokenError, so handle ExpiredIdTokenError first:

# 올바른 순서
except firebase_auth.ExpiredIdTokenError:  # 먼저!
    ...
except firebase_auth.InvalidIdTokenError:  # 나중에
    ...
 
# 잘못된 순서 - ExpiredIdTokenError가 InvalidIdTokenError로 처리됨
except firebase_auth.InvalidIdTokenError:
    ...
except firebase_auth.ExpiredIdTokenError:  # 절대 도달하지 않음
    ...

6. Implement the Authentication Router

app/routers/auth.py:

from fastapi import APIRouter, Depends
 
from app.dependencies import FirebaseUser, get_current_user
 
router = APIRouter(prefix="/auth", tags=["auth"])
 
@router.get("/me")
def get_me(user: FirebaseUser = Depends(get_current_user)):
    """현재 로그인한 사용자 정보 반환"""
    return {
        "uid": user.uid,
        "email": user.email,
        "name": user.name,
    }
 
@router.get("/verify")
def verify_token(user: FirebaseUser = Depends(get_current_user)):
    """토큰 유효성 검증"""
    return {
        "valid": True,
        "uid": user.uid,
    }

7. Register the Router in the Main App

app/main.py:

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
 
from app.config import get_settings
from app.routers import auth
 
settings = get_settings()
 
app = FastAPI(title="Backend API")
 
# CORS 설정
app.add_middleware(
    CORSMiddleware,
    allow_origins=settings.cors_origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
 
# 라우터 등록
app.include_router(auth.router)

Usage Examples

Calling the API

# 성공 케이스
curl -H "Authorization: Bearer <firebase-id-token>" \
  http://localhost:8000/auth/me
 
# 응답
{"uid": "abc123", "email": "user@example.com", "name": "John"}
# 토큰 없이 요청
curl http://localhost:8000/auth/me
 
# 응답 (403)
{"detail": "Not authenticated"}
# 잘못된 토큰
curl -H "Authorization: Bearer invalid_token" \
  http://localhost:8000/auth/me
 
# 응답 (401)
{"detail": "Invalid authentication token"}

Obtaining a Token on the Frontend

import { getAuth } from 'firebase/auth';
 
async function getIdToken(): Promise<string | null> {
  const auth = getAuth();
  const user = auth.currentUser;
 
  if (!user) return null;
 
  // forceRefresh: true로 항상 최신 토큰 획득
  return user.getIdToken(true);
}
 
// API 호출
const token = await getIdToken();
const response = await fetch('http://localhost:8000/auth/me', {
  headers: {
    'Authorization': `Bearer ${token}`,
  },
});

Project Structure

app/
├── main.py              # FastAPI 앱 진입점
├── config.py            # 설정 (pydantic-settings)
├── firebase.py          # Firebase Admin SDK 초기화
├── dependencies.py      # 인증 의존성 (get_current_user)
└── routers/
    └── auth.py          # 인증 엔드포인트

Security Considerations

  1. Protect the service-account key: add it to .gitignore and manage its path through an environment variable
  2. Use HTTPS: always use HTTPS in production
  3. Configure CORS: explicitly specify only permitted origins
  4. Handle token expiration: implement token-refresh logic on the frontend
  5. Error messages: be careful about exposing detailed errors in production

Wrapping Up

Connecting Firebase Authentication to FastAPI lets the backend securely verify the frontend's authentication state. The key points are:

  1. Verify ID tokens with the Firebase Admin SDK
  2. Implement reusable authentication logic with FastAPI dependencies
  3. Handle errors with the exception-class inheritance hierarchy in mind

Related posts

Comments

Korean and English pages share this conversation.

Write a comment

0 / 5,000
You will need this password to edit or delete this comment.

Loading comments…