22Authentication middleware for FastAPI.
33
44Protects routes based on authentication status and configuration.
5+ Supports both session cookies and Bearer tokens for OAuth 2.0.
56"""
67
78from fastapi import Request , HTTPException
8- from fastapi .responses import RedirectResponse
9+ from fastapi .responses import RedirectResponse , JSONResponse
910from starlette .middleware .base import BaseHTTPMiddleware
1011
1112from api .auth import is_auth_enabled , get_current_user
13+ from api .oauth import validate_bearer_token
1214
1315
1416# Paths that don't require authentication
2325 "/api/redoc" ,
2426 "/api/openapi.json" ,
2527 "/static" ,
26- "/favicon.ico"
28+ "/favicon.ico" ,
29+ "/register" , # OAuth dynamic client registration
2730}
2831
2932# Path prefixes that don't require authentication
3033PUBLIC_PREFIXES = (
3134 "/static/" ,
3235 "/auth/" ,
36+ "/.well-known/" , # OAuth discovery endpoints
37+ "/oauth/" , # OAuth endpoints (authorize, token, register)
3338)
3439
3540
@@ -73,21 +78,32 @@ async def dispatch(self, request: Request, call_next):
7378 if not is_auth_enabled ():
7479 return await call_next (request )
7580
76- # Check authentication
81+ # Check for Bearer token first (for MCP/API clients)
82+ auth_header = request .headers .get ("Authorization" , "" )
83+ if auth_header .startswith ("Bearer " ):
84+ token = auth_header [7 :] # Remove "Bearer " prefix
85+ token_data = validate_bearer_token (token )
86+ if token_data :
87+ # Token is valid, continue
88+ request .state .user = token_data
89+ return await call_next (request )
90+
91+ # Check session cookie (for browser users)
7792 user = get_current_user (request )
7893
7994 if not user :
80- # API routes return 401
95+ # API routes return 401 JSON response
8196 if path .startswith ("/api/" ) or path .startswith ("/mcp/" ):
82- raise HTTPException (
97+ return JSONResponse (
8398 status_code = 401 ,
84- detail = " Authentication required"
99+ content = { "detail" : " Authentication required"}
85100 )
86101
87102 # Browser routes redirect to login
88103 return RedirectResponse (url = "/auth/login" , status_code = 302 )
89104
90105 # User is authenticated, continue
106+ request .state .user = user
91107 return await call_next (request )
92108
93109
@@ -96,6 +112,7 @@ def require_auth(request: Request) -> dict:
96112 Dependency to require authentication.
97113
98114 Use as a FastAPI dependency on routes that need auth.
115+ Supports both session cookies and Bearer tokens.
99116
100117 Args:
101118 request: FastAPI request
@@ -109,11 +126,24 @@ def require_auth(request: Request) -> dict:
109126 if not is_auth_enabled ():
110127 return {"username" : "anonymous" , "authenticated" : False }
111128
129+ # Check for Bearer token first
130+ auth_header = request .headers .get ("Authorization" , "" )
131+ if auth_header .startswith ("Bearer " ):
132+ token = auth_header [7 :]
133+ token_data = validate_bearer_token (token )
134+ if token_data :
135+ return {
136+ "username" : token_data .get ("username" ),
137+ "authenticated" : True ,
138+ "auth_type" : "bearer"
139+ }
140+
141+ # Check session cookie
112142 user = get_current_user (request )
113143 if not user :
114144 raise HTTPException (
115145 status_code = 401 ,
116146 detail = "Authentication required"
117147 )
118148
119- return user
149+ return { ** user , "auth_type" : "session" }
0 commit comments