Session Fixation in Fastapi with Api Keys
Session Fixation in Fastapi with Api Keys — how this specific combination creates or exposes the vulnerability
Session fixation occurs when an application assigns a user a session identifier before authentication and fails to issue a new identifier after login. In FastAPI, using only API keys for authentication does not create a traditional session cookie, but it can still lead to a fixation-like condition if the API key is treated as a long-lived credential that is exchanged or reused across trust boundaries. For example, a client may obtain an API key through an unauthenticated endpoint, then present that same key to a privileged endpoint without re-validation. If the server does not rotate or re-issue the key after a privilege change, the key effectively remains fixed for the same identity across different security contexts.
Consider a scenario where an unauthenticated endpoint issues a temporary API key, and the client later uses that key to access authenticated routes. If the server does not enforce scope or context separation, an attacker who can predict or obtain the key before privilege escalation may retain access to higher-privileged operations. This becomes a fixation risk when the same key is reused across different user roles or when the key is exposed in logs, URLs, or error messages. Because API keys are often static or long-lived compared to session tokens, they can be more susceptible to leakage through insecure transport, client-side storage, or logging practices.
Additionally, if FastAPI applications expose OpenAPI specs that describe key-based flows without clarifying key rotation or scope boundaries, external scanners can infer trust assumptions. An API key used across multiple services or routes without per-request validation can amplify the impact of exposure. Attackers may attempt to correlate unauthenticated endpoints that return keys with authenticated endpoints that accept them, effectively chaining discovery paths. The risk is not in FastAPI’s core behavior but in design choices that treat API keys as session-like identifiers without adequate invalidation or reassignment mechanisms.
Api Keys-Specific Remediation in Fastapi — concrete code fixes
To mitigate fixation-like risks with API keys in FastAPI, rotate or re-validate the key context after authentication or privilege changes, and enforce strict scope separation. Avoid reusing the same key across different trust levels, and prefer short-lived keys where feasible. Use explicit dependencies to validate key scope per route and ensure that keys are never exposed in URLs or logs.
The following example demonstrates secure API key usage in FastAPI with scope-based validation and key rotation principles. It does not implement key rotation automatically—middleBrick does not provide automatic remediation—but it shows how to structure dependencies to reduce fixation risk by enforcing per-route validation and avoiding key reuse across privilege levels.
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import APIKeyHeader
app = FastAPI()
api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
# Simulated key store with scopes
VALID_KEYS = {
"public-key-123": {"scope": "read", "subject": "public"},
"admin-key-456": {"scope": "write", "subject": "admin"},
}
def get_key_scope(key: str = Depends(api_key_header)):
if not key:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="API key missing",
)
if key not in VALID_KEYS:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Invalid API key",
)
return VALID_KEYS[key]
def require_scope(required_scope: str):
def scope_guard(key_info: dict = Depends(get_key_scope)):
if key_info["scope"] != required_scope:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"Insufficient scope: expected {required_scope}",
)
return key_info
return scope_guard
@app.get("/public")
def read_public(key_info: dict = Depends(get_key_scope)):
# Public scope allowed for read operations
return {"message": "public data", "scope": key_info["scope"]}
@app.post("/admin")
def perform_admin(key_info: dict = Depends(require_scope("write"))):
# Enforce write scope for admin actions
return {"message": "admin action executed", "subject": key_info["subject"]}
@app.get("/keys/rotate")
def rotate_key_example(
old_key: str = Depends(api_key_header),
):
# Example handler illustrating key rotation guidance.
# In practice, key rotation should be handled via secure backend workflows.
if old_key not in VALID_KEYS:
raise HTTPException(status_code=401, detail="Invalid key")
# Do not generate or return new keys from this endpoint in production.
# This is a placeholder to highlight where rotation logic would be triggered.
return {"hint": "Implement key rotation outside this endpoint"}
In this example, each route declares its required scope via require_scope, ensuring that a key granted for read operations cannot be reused for write actions. The get_key_scope dependency validates the key on every request, avoiding implicit trust. To further reduce fixation risk, avoid exposing keys in query strings or logs, and prefer short-lived keys with secure rotation workflows handled outside the API surface. The Pro plan’s continuous monitoring can help detect anomalies in key usage patterns across your scanned APIs.