HIGH api key exposurefastapioauth2

Api Key Exposure in Fastapi with Oauth2

Api Key Exposure in Fastapi with Oauth2 — how this specific combination creates or exposes the vulnerability

Api Key Exposure occurs when an API key meant to identify or authorize a client is unintentionally returned to an attacker. In Fastapi applications that also use Oauth2 for user authentication, this risk can arise from misconfigured endpoints, overly permissive scopes, or incorrect token introspection logic. When an endpoint accepts both an Oauth2 bearer token and an API key (for example, via an x-api-key header), and either value is reflected in error messages, logs, or responses without proper validation, an attacker may learn the key.

Fastapi’s dependency injection system makes it straightforward to enforce Oauth2 flows, but developers can inadvertently expose keys if they propagate unsafe data into responses or logs. For instance, returning the raw token from an introspection call, including the key in a JSON response during debugging, or failing to separate scopes for service-to-service calls can all contribute to leakage. Attackers may then use the exposed key to impersonate services, bypass intended rate limits, or pivot to other internal APIs.

Another vector specific to this combination is mismatched audience or scope validation. If an Oauth2 token validation routine does not strictly verify the aud claim against the intended resource and also does not enforce scope checks for key-requiring endpoints, an attacker with a stolen key might use a low-privilege token to access high-privilege routes that return the key in error payloads. This can be particularly impactful when the key is embedded in HTTP headers that are echoed in responses or when the API includes the key in links or redirects that are returned to the client.

Consider an endpoint that validates Oauth2 tokens but still expects an x-api-key header for service-to-service calls. If the endpoint’s error handling inadvertently includes the header value in a 400 response, or if the OpenAPI schema describes the header without clarifying that it must remain confidential, an unauthenticated or low-privileged caller can learn the key through introspection or fuzzing. This violates the principle of least privilege and can lead to privilege escalation when the exposed key grants higher permissions than the associated Oauth2 scope.

Oauth2-specific risks in this context also include authorization code interception or token leakage via insecure redirects. If the Fastapi application uses a redirect URI that exposes tokens in query parameters and also requires an API key for certain operations, an attacker observing the redirect can capture both the token and any related keying material. Incorrect use of public clients versus confidential clients further blurs the boundary between user-level and service-level credentials, increasing the chance of key exposure.

To detect and understand these patterns, scanning with a tool that supports OpenAPI/Swagger 2.0, 3.0, and 3.1 with full $ref resolution is valuable. Such analysis cross-references the spec definitions with runtime behavior to highlight endpoints that accept sensitive headers like x-api-key alongside Oauth2 security schemes. This helps identify mismatches between declared scopes, audience restrictions, and actual endpoint behavior that can lead to inadvertent key disclosure.

Oauth2-Specific Remediation in Fastapi — concrete code fixes

Remediation focuses on strict validation, separation of concerns, and minimizing exposure of sensitive material in responses and logs. In Fastapi, define a clear security scheme using OAuth2PasswordBearer or OAuth2AuthorizationCodeBearer, and enforce scope and audience checks within dependencies. Avoid echoing tokens or keys in responses, and ensure errors are generic.

Below is a secure Fastapi example that uses Oauth2 with explicit scope validation and avoids exposing API keys in responses. The API key is required only for selected service-to-service routes and is validated independently of the Oauth2 token, with no leakage into responses or logs.

from fastapi import Fastapi, Depends, HTTPException, Header, Security, status
from fastapi.security import OAuth2PasswordBearer, OAuth2SecurityScopes
from pydantic import BaseModel

app = Fastapi()

oauth2_scheme = OAuth2PasswordBearer(
    tokenUrl="token",
    scopes={"read": "Read data", "write": "Write data"}
)

# Security dependency that validates scopes
async def verify_scopes(token: str = Depends(oauth2_scheme), required_scopes: list[str] = []):
    # Replace with real introspection or JWT validation that checks 'scope' and 'aud'
    # For example, decode JWT and verify scopes and audience here
    if not token or token == "invalid":
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid authentication credentials",
            headers="WWW-Authenticate",
        )
    # Pseudo-validation: ensure required scopes are present
    token_scopes = {"read", "write"}  # obtained from token claims
    missing = set(required_scopes) - token_scopes
    if missing:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Insufficient scope",
        )
    return token

# API key validation separate from OAuth2
def verify_api_key(x_api_key: str = Header(..., alias="x-api-key")):
    # Compare against a stored key securely (e.g., constant-time comparison)
    valid_key = "super-secret-service-key"
    if x_api_key != valid_key:
        raise HTTPException(status_code=403, detail="Invalid API key")
    return x_api_key

@app.get("/public")
async def public_data():
    return {"message": "public endpoint, no auth required"}

@app.get("/data")
async def read_data(token: str = Security(verify_scopes, required_scopes=["read"])):
    # No API key required for this endpoint
    return {"data": "protected data"}

@app.get("/admin")
async def admin_action(
    token: str = Security(verify_scopes, required_scopes=["read", "write"]),
    api_key: str = Security(verify_api_key)
):
    # Both valid token and API key are required
    return {"result": "admin operation performed"}

@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    # Issue token with appropriate scopes and audience
    # In practice, sign JWT with 'aud' matching the API identifier
    return {"access_token": "example-jwt-with-scopes", "token_type": "bearer"}

Key remediation practices:

  • Never return tokens or API keys in responses or logs.
  • Validate both scope and audience on each token verification.
  • Use separate security dependencies for user context (OAuth2) and service identity (API key).
  • Ensure OpenAPI schemas describe which headers are sensitive but avoid exposing example values that could be misused.
  • Apply consistent error handling to prevent information leakage in error payloads.

These steps reduce the likelihood of accidental disclosure and help ensure that the combination of Oauth2 and API keys does not introduce a leakage path.

Frequently Asked Questions

How can I test whether my Fastapi endpoints leak API keys in error responses?
Send intentionally malformed OAuth2 tokens or missing/incorrect x-api-key headers to each endpoint and inspect responses for echoing of the key or internal values. Use automated scanning that includes header exposure checks to detect patterns where keys appear in JSON bodies or logs.
Does using OAuth2 eliminate the need for API keys in Fastapi?
No. OAuth2 can handle user and application authentication and authorization, but API keys are often required for service-to-service authentication, quota management, or routing. The key is to require both where appropriate and ensure keys are never exposed in responses, logs, or URLs.