"""API key authentication middleware for FastAPI."""
from fastapi import Request, HTTPException
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
from .config import config
class APIKeyMiddleware(BaseHTTPMiddleware):
"""Middleware to validate API key from request headers.
Supports two authentication methods:
- Authorization: Bearer <token>
- x-api-key: <token>
"""
# Paths that don't require authentication
EXEMPT_PATHS = {"/health", "/", "/docs", "/openapi.json", "/redoc"}
async def dispatch(self, request: Request, call_next):
# Skip auth for exempt paths
if request.url.path in self.EXEMPT_PATHS:
return await call_next(request)
# Skip auth if no API key is configured (development mode)
if not config.API_KEY:
return await call_next(request)
# Extract API key from headers
api_key = self._extract_api_key(request)
if not api_key:
return JSONResponse(
status_code=401,
content={"error": "Missing API key. Provide via 'Authorization: Bearer <token>' or 'x-api-key' header."}
)
if api_key != config.API_KEY:
return JSONResponse(
status_code=401,
content={"error": "Invalid API key."}
)
return await call_next(request)
def _extract_api_key(self, request: Request) -> str | None:
"""Extract API key from request headers."""
# Try Authorization header first (Bearer token)
auth_header = request.headers.get("Authorization", "")
if auth_header.startswith("Bearer "):
return auth_header[7:]
# Fall back to x-api-key header
return request.headers.get("x-api-key")
def validate_api_key(api_key: str | None) -> bool:
"""Validate an API key against the configured key.
Args:
api_key: The API key to validate
Returns:
True if valid or no key configured, False otherwise
"""
if not config.API_KEY:
return True
return api_key == config.API_KEY