ruby2210/todo-manager
0
1from fastapi import Request, HTTPException, status2from fastapi.security.http import HTTPBearer, HTTPAuthorizationCredentials3from typing import Optional4from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint5from starlette.responses import Response6from src.auth.utils import verify_token7 8 9class AuthMiddleware(BaseHTTPMiddleware):10 """11 Global authentication middleware for all /api/ routes.12 13 This middleware automatically validates JWT tokens for all API routes14 and attaches user information to the request state.15 """16 17 def __init__(self, app, exempt_paths: Optional[list] = None):18 super().__init__(app)19 self.exempt_paths = exempt_paths or [20 "/health",21 "/docs",22 "/redoc",23 "/openapi.json",24 "/api/auth/login",25 "/api/auth/signup",26 ]27 self.security = HTTPBearer(28 auto_error=False29 ) # Don't auto-error, handle manually30 31 async def dispatch(32 self, request: Request, call_next: RequestResponseEndpoint33 ) -> Response:34 # Skip authentication for exempt paths35 if request.url.path in self.exempt_paths or request.url.path.startswith(36 "/api/docs"37 ):38 response = await call_next(request)39 return response40 41 # Skip authentication for non-API routes (for now, we'll protect all /api/ routes)42 if (43 not request.url.path.startswith("/api/")44 or request.url.path == "/api/health"45 ):46 response = await call_next(request)47 return response48 49 # Extract authorization header50 auth_header = request.headers.get("Authorization")51 if not auth_header:52 raise HTTPException(53 status_code=status.HTTP_401_UNAUTHORIZED,54 detail="Authorization header missing",55 headers={"WWW-Authenticate": "Bearer"},56 )57 58 # Verify token59 token = auth_header.replace("Bearer ", "")60 payload = verify_token(token)61 62 if not payload:63 raise HTTPException(64 status_code=status.HTTP_401_UNAUTHORIZED,65 detail="Invalid or expired token",66 headers={"WWW-Authenticate": "Bearer"},67 )68 69 # Add user info to request state for use in endpoints70 user_id = payload.get("sub") or payload.get("user_id")71 if user_id:72 request.state.user_id = user_id73 else:74 raise HTTPException(75 status_code=status.HTTP_401_UNAUTHORIZED,76 detail="User ID not found in token",77 headers={"WWW-Authenticate": "Bearer"},78 )79 80 response = await call_next(request)81 return response82 83 84# Alternative: A simpler approach using dependency injection at the router level85# This is often preferred in FastAPI applications86from fastapi import Depends87from src.api.deps import get_current_user88 89 90def get_auth_dependency():91 """92 Returns the authentication dependency for use in routes.93 This can be used to add authentication to specific routes or routers.94 """95 return Depends(get_current_user)96 