Api Key Exposure in Fastapi with Jwt Tokens
Api Key Exposure in Fastapi with Jwt Tokens — how this specific combination creates or exposes the vulnerability
In FastAPI applications that use JWT tokens for authentication, developers sometimes inadvertently expose static API keys through insecure handling, logging, or configuration. When API keys are embedded in route logic, middleware, or debug output alongside JWT validation, they may be returned in responses or logged in plaintext. This occurs when authorization logic conflates API-level access control with token-based identity and exposes key material through verbose error messages or misconfigured CORS and exception handlers.
A typical misconfiguration is attaching an API key as a header or query parameter and passing it through to downstream services without scrubbing it. For example, a route that forwards an incoming Authorization header to a backend microservice may propagate the key if the JWT payload is used to construct outbound requests without removing sensitive headers. MiddleBrick tests such cross-layer exposures as part of Data Exposure and Unsafe Consumption checks, identifying when keys leak through responses or logs while JWT validation appears intact.
Another scenario involves OpenAPI spec generation where paths reference securitySchemes that mix an API key in a header with an HTTP bearer scheme using JWT tokens. If the spec defines multiple security requirements and runtime validation does not enforce strict separation, clients may receive key material in examples or schema descriptions. Additionally, during unauthenticated LLM endpoint probing, an attacker could attempt to coax key disclosure via crafted prompts if the endpoint reflects authorization details in error payloads.
Middleware that logs request and response headers indiscriminately may capture and persist API keys when exceptions occur during JWT decoding. If key identifiers appear in URLs or query strings, they may be stored in access logs and later exfiltrated. Since JWT tokens are often validated early, developers may assume downstream calls are safe, but failing to strip or mask sensitive identifiers before logging or forwarding creates an inadvertent channel for key exposure.
Dependency on environment variables is common, yet incorrect usage can lead to keys being serialized into error responses. For instance, catching validation errors from PyJWT and returning the raw exception detail may expose configuration keys if the error message includes variable interpolation. This is especially risky when combined with overly permissive CORS or when health endpoints return configuration metadata. MiddleBrick’s Data Exposure and Property Authorization checks flag these patterns by correlating spec definitions with runtime responses.
Jwt Tokens-Specific Remediation in Fastapi — concrete code fixes
To remediate exposure when using JWT tokens in FastAPI, enforce strict separation between identity claims and API keys, and ensure keys never appear in responses or logs. Use dependency injection to validate JWTs and explicitly drop sensitive headers before forwarding requests. Below are concrete, working examples that illustrate secure patterns.
Secure JWT validation without leaking headers
from fastapi import FastAPI, Depends, Header, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt
from typing import Optional
app = FastAPI()
security_scheme = HTTPBearer()
def decode_jwt(token: str) -> dict:
# Use your public key or secret and appropriate algorithms
return jwt.decode(token, options={"verify_exp": True})
async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security_scheme)):
try:
payload = decode_jwt(credentials.credentials)
return payload
except jwt.PyJWTError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token",
headers={"WWW-Authenticate": "Bearer"},
)
@app.get("/items/")
async def read_items(user: dict = Depends(get_current_user), x_api_key: Optional[str] = Header(None)):
# Explicitly do not forward or log x_api_key; if required, scrub it before any external call
if x_api_key:
# Validate or map key internally without reflecting it back
pass
return {"data": "safe"}
Removing sensitive headers before outbound calls
import httpx
from fastapi import Depends
def build_httpx_client():
return httpx.AsyncClient()
async def call_backend_service(
user: dict = Depends(get_current_user),
api_key: Optional[str] = Header(None),
client: httpx.AsyncClient = Depends(build_httpx_client)
):
headers = {"Authorization": f"Bearer {user['sub']}"}
# Never forward incoming API key; if backend requires a key, retrieve it securely from a vault
# Do not include 'api_key' in headers if it originated from the client
response = await client.get("https://internal.service/data", headers=headers, timeout=10.0)
response.raise_for_status()
return response.json()
Logging and error handling hygiene
Ensure that log statements exclude header values and that exception handlers do not serialize sensitive data:
import logging
from fastapi import Request
logger = logging.getLogger("api")
@app.middleware("http")
async def scrub_logging_middleware(request: Request, call_next):
# Avoid logging headers that may contain keys
safe_headers = {k: "***" if k.lower() == "authorization" else v for k, v in request.headers.items()}
logger.info(f"Request: {request.method} {request.url} headers={safe_headers}")
response = await call_next(request)
return response
OpenAPI spec hygiene
When using FastAPI’s automatic docs, avoid mixing security schemes that imply key usage in JWT-secured endpoints. Define separate security schemes and apply them per-path to prevent examples from exposing keys.
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi
app = FastAPI()
app.security_schemes = {
"bearerAuth": {"type": "http", "scheme": "bearer", "bearerFormat": "JWT"},
# Do not define an API key scheme for endpoints that use JWT only
}
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title="Secure API",
version="1.0.0",
routes=app.routes,
)
# Ensure paths using JWT do not reference an API key security requirement
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi