Api Key Exposure in Fastapi
How Api Key Exposure Manifests in Fastapi
Api Key Exposure in Fastapi applications typically occurs through several Fastapi-specific patterns. The most common vulnerability appears when developers implement custom authentication middleware that inadvertently logs or exposes API keys in error responses. For example, a naive implementation might catch exceptions and return the raw API key in the response body:
@app.exception_handler(APIException)
async def api_exception_handler(request: Request, exc: APIException):
return JSONResponse(
status_code=exc.status_code,
content={"detail": f"Invalid API key: {exc.api_key}", "key": exc.api_key}
)
This pattern directly exposes the API key to any client making an invalid request, violating confidentiality principles.
Another Fastapi-specific manifestation occurs with dependency injection. Developers often create API key dependencies that are too permissive:
async def get_api_key(
x_api_key: str = Header(...),
deprecated_api_key: str = Header(None)
):
# Accepts both modern and deprecated keys without validation
return x_api_key or deprecated_api_key
@app.get("/sensitive-data")
async def read_data(api_key: str = Depends(get_api_key)):
# No actual validation performed
return sensitive_data
This creates a situation where any header value is accepted, effectively disabling authentication.
Fastapi's automatic OpenAPI documentation generation can also expose API keys. When API key parameters are defined in path operations without proper security schemes, they appear in the generated OpenAPI spec:
@app.get("/users/me")
async def read_user_me():
return {"username": "dummy"}
# Missing security scheme definition
Without proper security scheme configuration, API keys become visible in the interactive API documentation, allowing anyone with access to the docs to discover valid endpoints and authentication requirements.
Rate limiting implementations in Fastapi can also lead to key exposure. A common anti-pattern involves logging API keys for debugging:
@app.middleware("http")
async def api_key_logging_middleware(request: Request, call_next):
api_key = request.headers.get("x-api-key")
logger.info(f"API key {api_key} accessed endpoint {request.url.path}")
response = await call_next(request)
return response
This logs API keys in plaintext, creating a security audit trail that contains sensitive credentials.
Fastapi-Specific Detection
Detecting API key exposure in Fastapi requires examining both code patterns and runtime behavior. Static analysis should look for these specific Fastapi patterns:
Header-based authentication without validation:
# Vulnerable pattern - accepts any header value
async def get_api_key(x_api_key: str = Header(...)):
return x_api_key
# Secure pattern - validates against known keys
async def get_api_key(x_api_key: str = Header(...)):
if x_api_key not in VALID_API_KEYS:
raise HTTPException(status_code=401, detail="Invalid API key")
return x_api_key
Exception handlers that expose credentials:
# Vulnerable - returns raw API key in response
@app.exception_handler(APIException)
async def handler(request, exc):
return JSONResponse(content={"key": exc.api_key})
# Secure - generic error message only
@app.exception_handler(APIException)
async def handler(request, exc):
return JSONResponse(status_code=401, content={"detail": "Unauthorized"})
Logging sensitive data:
# Vulnerable - logs API keys
logger.info(f"User {api_key} accessed {endpoint}")
# Secure - logs only user IDs after authentication
logger.info(f"User {user_id} accessed {endpoint}")
Runtime detection with middleBrick specifically targets Fastapi applications by scanning the unauthenticated attack surface. The scanner identifies API key exposure through:
- Header analysis: Testing for predictable API key header names (x-api-key, authorization, api-key) and attempting authentication bypass
- Response analysis: Checking for API keys in error responses, headers, or response bodies
- Documentation analysis: Examining OpenAPI specs for exposed authentication schemes
- Rate limiting bypass: Testing whether rate limiting is properly keyed to authenticated users
middleBrick's Fastapi-specific detection includes active probing for common Fastapi authentication patterns and validation of proper error handling. The scanner tests whether invalid API keys produce generic error messages or leak information about valid keys.
Command-line scanning with middleBrick:
middlebrick scan https://api.example.com --fastapi --output json
This command enables Fastapi-specific security checks and outputs findings in JSON format for integration with other tools.
Fastapi-Specific Remediation
Remediating API key exposure in Fastapi requires implementing secure authentication patterns using Fastapi's built-in features. The most secure approach uses Fastapi's dependency injection system with proper validation:
Secure API key dependency:
from fastapi import HTTPException, Depends, status
from typing import Optional
from jose import jwt
# Store API keys securely (never in code)
API_KEYS_DB = {
"valid-key-123": {"user_id": 1, "permissions": ["read"]},
"admin-key-abc": {"user_id": 2, "permissions": ["read", "write"]}
}
def get_api_key(
x_api_key: str = Header(...),
deprecated_api_key: Optional[str] = Header(None)
):
"""Secure API key validation with deprecation support"""
key = x_api_key or deprecated_api_key
if key not in API_KEYS_DB:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid API key",
headers={"WWW-Authenticate": "Bearer"}
)
return API_KEYS_DB[key]
async def get_current_user(api_key_data: dict = Depends(get_api_key)):
"""Return authenticated user information"""
return api_key_data
Secure exception handling:
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
"""Generic error response without sensitive data"""
return JSONResponse(
status_code=exc.status_code,
content={"detail": "Authentication failed" if exc.status_code == 401 else exc.detail}
)
Rate limiting with API key awareness:
from fastapi import Request
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
limiter = Limiter(key_func=get_remote_address)
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
async def api_key_rate_limit(request: Request, call_next):
api_key = request.headers.get("x-api-key")
if api_key:
# Rate limit per API key
key = f"apikey:{api_key}"
else:
# Rate limit by IP for unauthenticated requests
key = get_remote_address(request)
if not limiter.is_allowed(key, 100, 3600):
raise RateLimitExceeded()
return await call_next(request)
Secure logging practices:
import logging
from uuid import uuid4
logger = logging.getLogger(__name__)
def get_api_key(request: Request):
api_key = request.headers.get("x-api-key")
if api_key:
# Log only key hash for correlation
key_hash = hash(api_key)
logger.info(f"Authenticated request from key hash {key_hash}")
return api_key
OpenAPI security scheme configuration:
from fastapi import FastAPI, Security
from fastapi.security.api_key import APIKeyHeader
api_key_scheme = APIKeyHeader(name="x-api-key", scheme_name="API Key")
@app.get("/secure-endpoint", security=[api_key_scheme])
async def secure_endpoint(
api_key: str = Security(api_key_scheme)
):
return {"message": "Authenticated"}
This configuration ensures API keys are properly documented in OpenAPI specs without exposing them in the interactive documentation. middleBrick can verify that these security schemes are correctly implemented and that no API keys are exposed through documentation.