1- from fastapi import Request , HTTPException
1+ from fastapi import Request
2+ from typing import List
3+ import logging
4+ import firebase_admin
25from firebase_admin import auth
3- from typing import Optional , List
4- from functools import wraps
6+ from fastapi .responses import JSONResponse
7+
8+ logger = logging .getLogger (__name__ )
59
610class FirebaseAuthMiddleware :
711 def __init__ (self , app , exclude_paths : List [str ] = None ):
812 self .app = app
913 self .exclude_paths = exclude_paths or []
14+ logger .info ("FirebaseAuthMiddleware initialized" )
1015
11- async def __call__ (self , request : Request , call_next ):
12- if request . url . path in self . exclude_paths :
13- return await call_next ( request )
16+ async def __call__ (self , scope , receive , send ):
17+ if scope [ "type" ] != "http" :
18+ return await self . app ( scope , receive , send )
1419
15- # Get the Authorization header
16- authorization = request .headers .get ("Authorization" )
17- if not authorization or not authorization .startswith ("Bearer " ):
18- raise HTTPException (
19- status_code = 401 ,
20- detail = "Missing or invalid authorization header"
21- )
20+ request = Request (scope , receive )
21+
22+ # Skip excluded paths
23+ if request .url .path in self .exclude_paths :
24+ logger .debug (f"Path { request .url .path } is excluded from auth check" )
25+ return await self .app (scope , receive , send )
2226
27+ # Extract token from headers
2328 try :
24- # Verify Firebase token
25- token = authorization .split ("Bearer " )[1 ]
26- decoded_token = auth .verify_id_token (token )
29+ authorization = request .headers .get ("Authorization" , "" )
30+ if not authorization or not authorization .startswith ("Bearer " ):
31+ return await self .send_error_response (
32+ scope , receive , send ,
33+ status_code = 401 ,
34+ detail = "Missing or invalid authorization header"
35+ )
2736
28- # Add user info to request state
29- request .state .user_id = decoded_token .get ("uid" )
30- request .state .user_claims = decoded_token .get ("claims" , {})
37+ token = authorization .replace ("Bearer " , "" )
3138
32- response = await call_next (request )
33- return response
34-
39+ try :
40+ # Verify the Firebase token
41+ decoded_token = auth .verify_id_token (token )
42+ user_id = decoded_token .get ("uid" )
43+
44+ # Add user info to request state
45+ request .state .user_id = user_id
46+ request .state .firebase_user = decoded_token
47+ request .state .user_claims = decoded_token .get ("claims" , {})
48+ logger .debug (f"Authenticated user: { user_id } " )
49+
50+ # Continue with the request
51+ return await self .app (scope , receive , send )
52+
53+ except firebase_admin .exceptions .FirebaseError as e :
54+ logger .error (f"Firebase auth error: { str (e )} " )
55+ return await self .send_error_response (
56+ scope , receive , send ,
57+ status_code = 401 ,
58+ detail = "Invalid or expired token"
59+ )
3560 except Exception as e :
36- raise HTTPException (
37- status_code = 401 ,
38- detail = f"Invalid authentication credentials: { str (e )} "
61+ logger .exception (f"Unexpected error in auth middleware: { str (e )} " )
62+ return await self .send_error_response (
63+ scope , receive , send ,
64+ status_code = 401 ,
65+ detail = "Authentication error"
3966 )
4067
41- def require_roles (roles : List [str ]):
42- """Dependency for role-based access control"""
43- async def role_checker (request : Request ):
44- user_roles = request .state .user_claims .get ("roles" , [])
45- if not any (role in user_roles for role in roles ):
46- raise HTTPException (
47- status_code = 403 ,
48- detail = "Insufficient permissions"
49- )
50- return True
51- return role_checker
68+ async def send_error_response (self , scope , receive , send , status_code , detail ):
69+ response = JSONResponse (
70+ status_code = status_code ,
71+ content = {"detail" : detail }
72+ )
73+ await response (scope , receive , send )
0 commit comments