Api Key Exposure in Fastapi with Api Keys
Api Key Exposure in Fastapi with Api Keys — how this specific combination creates or exposes the vulnerability
API key exposure in FastAPI applications using an Api Keys security scheme occurs when keys intended for authorization are inadvertently accessible through unauthenticated endpoints or misconfigured OpenAPI documentation. This combination creates a risk where an attacker can discover valid keys without needing credentials to access the API itself.
In FastAPI, an API key is typically defined using HTTPBearer or APIKeyHeader. If an endpoint is declared without requiring the security scheme, or if the OpenAPI schema reveals which header names are used for authentication, an attacker gains reconnaissance data that facilitates key discovery. For example, exposing the header name x-api-key in the public spec informs an attacker exactly where to probe for valid keys.
Another common exposure vector is serving the OpenAPI specification on a public route without authentication. Since the spec includes the securitySchemes definition and endpoint paths, it effectively becomes a guide for automated scanning. An attacker can use the spec to enumerate endpoints and test for keys that may have been accidentally committed to client-side code, logs, or error messages.
Middleware or custom dependencies that log incoming headers can also contribute to exposure. If logs retain full request headers, API keys may appear in centralized logging systems, increasing the blast radius of a log access compromise. This is particularly dangerous when combined with weak access controls on log aggregation platforms.
Consider a FastAPI app that defines a key header but does not enforce it consistently:
from fastapi import FastAPI, Depends, Header, HTTPException
from fastapi.security import APIKeyHeader
app = FastAPI()
api_key_header = APIKeyHeader(name="x-api-key", auto_error=False)
def get_api_key(key: str = Header(...)):
if key != "super-secret-key":
raise HTTPException(status_code=403, detail="Invalid key")
return key
@app.get("/public-endpoint")
def public_endpoint(x_key: str = Depends(api_key_header)):
return {"message": "public but requires key"}
@app.get("/openapi")
def openapi_route():
return app.openapi()
In this pattern, /public-endpoint declares a dependency on the key header, but auto_error=False allows the endpoint to be reached without a key, returning a 403 only after processing. Meanwhile, /openapi exposes the full schema, including the exact header name, enabling scanners to confirm the authorization mechanism without authentication. This illustrates how design choices in FastAPI can inadvertently enable API key exposure during reconnaissance.
SSRF and client-side leakage further amplify the risk. If a FastAPI service fetches external documentation or dynamically builds routes based on user input, an attacker may force the service to disclose its expected headers through error handling or redirect behavior. Similarly, if API clients embed keys in URLs or query parameters for convenience, those keys can leak via browser history, server logs, or referrer headers, making header-based schemes preferable when implemented with strict dependency enforcement.
Api Keys-Specific Remediation in Fastapi — concrete code fixes
To remediate API key exposure in FastAPI, enforce strict security dependencies, hide header names from public schemas, and avoid leaking keys through logs or error messages. The goal is to ensure that valid keys are required for all sensitive endpoints and that the API’s surface does not reveal authorization details to unauthenticated visitors.
First, require the security scheme on all routes that need protection and disable automatic error generation to prevent early bypass:
from fastapi import FastAPI, Depends, Header, HTTPException
from fastapi.security import APIKeyHeader
app = FastAPI()
api_key_header = APIKeyHeader(name="x-api-key", auto_error=False)
def require_api_key(key: str = Header(...)):
if key != "super-secret-key":
raise HTTPException(status_code=401, detail="Unauthorized")
return key
@app.get("/secure-data", dependencies=[Depends(require_api_key)])
def secure_data():
return {"data": "protected"}
Second, remove the security scheme from the public OpenAPI definition by filtering it out before serving the spec:
@app.get("/openapi")
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi = app.openapi()
# Remove security requirement from all paths
for path in openapi.get("paths", {}).values():
for method in path.values():
method.pop("security", None)
# Optionally remove securitySchemes entirely
openapi.pop("components", None)
return openapi
This approach ensures that unauthenticated visitors cannot infer which headers are used for authorization. In production, you may choose to keep the schema but omit the security field from individual operations rather than removing components entirely.
Third, avoid logging headers and enforce strict key validation in a centralized dependency:
import logging
logger = logging.getLogger("api")
def require_api_key_safe(key: str = Header(...)):
# Never log raw keys
if not key or key != "super-secret-key":
logger.warning("Unauthorized access attempt")
raise HTTPException(status_code=401, detail="Unauthorized")
return key
Finally, prefer environment variables for key storage instead of hardcoding values, and rotate keys regularly. Combine these practices with middleware that normalizes header names and rejects malformed requests to reduce the attack surface for API key exposure in FastAPI services.