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
- The user signs in with Firebase on the frontend
- Firebase issues an ID token
- The frontend includes the token in the
Authorization: Bearer <token>header when calling the API - The backend verifies the token with the Firebase Admin SDK
- On successful verification, extract the user information and process the request
Implementation
1. Install Dependencies
pip install firebase-adminAdd to requirements.txt:
firebase-admin==6.4.0
2. Obtain a Firebase Service-Account Key
- Open the Firebase Console
- Project settings → Service accounts → Generate new private key
- 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_tokenKey points:
- Use the singleton pattern to avoid initializing the Firebase app more than once
verify_id_tokenautomatically 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
- Protect the service-account key: add it to
.gitignoreand manage its path through an environment variable - Use HTTPS: always use HTTPS in production
- Configure CORS: explicitly specify only permitted origins
- Handle token expiration: implement token-refresh logic on the frontend
- 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:
- Verify ID tokens with the Firebase Admin SDK
- Implement reusable authentication logic with FastAPI dependencies
- Handle errors with the exception-class inheritance hierarchy in mind





Comments
Korean and English pages share this conversation.
Loading comments…