Broken Access Control in Fastapi with Api Keys
Broken Access Control in Fastapi with Api Keys — how this specific combination creates or exposes the vulnerability
Broken Access Control occurs when API endpoints do not properly enforce authorization checks, allowing one user to access or modify resources belonging to another. In Fastapi, using only API keys as the authentication mechanism can inadvertently enable this vulnerability if keys are treated as equivalent to robust identity and authorization logic.
API keys are typically bearer tokens that identify an application or service, but they do not inherently convey user-level permissions. When developers map an API key directly to a role or set of permissions without validating the specific resource ownership or intent, they may create a BOLA/IDOR or BFLA/Privilege Escalation condition. For example, an endpoint like /users/{user_id}/settings might check that the request includes a valid API key, but fail to verify that the key’s associated scope or owner matches the requested user_id. This mismatch means an attacker who obtains or guesses a valid key can traverse other users’ data simply by changing the user_id parameter.
In Fastapi, this often arises when authorization is conflated with authentication. If the API key is verified in a global dependency that sets a generic security context, subsequent route handlers may skip per-resource checks. Consider a handler that retrieves a tenant ID from the key’s metadata but does not enforce tenant isolation on database queries. An attacker could then issue requests with valid keys but malicious paths, such as /tenant/123/invoices/456, and if the server only checks that the key belongs to any tenant, they may gain access to invoices belonging to other tenants (BOLA).
The 12 parallel security checks in middleBrick include Authentication, BOLA/IDOR, and BFLA/Privilege Escalation, and they are designed to surface these classes of misconfiguration. By testing unauthenticated attack surfaces, the scanner can detect whether endpoints that should be constrained by resource ownership or role boundaries are accessible with only a valid API key. Findings typically highlight missing per-request authorization, overly permissive key scopes, and routes that rely on client-supplied identifiers without re-verifying permissions.
OpenAPI/Swagger spec analysis (2.0, 3.0, 3.1) with full $ref resolution allows middleBrick to cross-reference declared security schemes with runtime behavior. If the spec defines an apiKey security scheme but the implementation lacks path-level or operation-level authorization checks, the discrepancy is flagged. This helps teams understand how a simple key-based setup can still lead to critical access control flaws when not paired with explicit, resource-aware logic.
Api Keys-Specific Remediation in Fastapi — concrete code fixes
To mitigate Broken Access Control when using API keys in Fastapi, you must enforce strict mapping between the key and the permissions or data scope it is allowed to access, and validate any user- or resource-level identifiers on every request.
Below are concrete, working examples that demonstrate a safer pattern. The first example shows a minimal, secure dependency that validates the API key and extracts allowed scopes and associated tenant or user constraints. The second example demonstrates how to enforce tenant isolation in a database query by combining the key’s metadata with the path parameter.
from fastapi import Depends, Fastapi, HTTPException, status
from typing import Dict, List
app = Fastapi()
# Simulated key store: in production, use a secure lookup service or database
API_KEYS = {
"sk_live_abc123": {"scopes": ["read:invoices"], "tenant_id": "tenant_a"},
"sk_live_xyz789": {"scopes": ["read:invoices", "write:invoices"], "tenant_id": "tenant_b"},
}
def get_current_key(api_key: str = Header(None)):
if not api_key or api_key not in API_KEYS:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid API key",
)
return API_KEYS[api_key]
# Dependency that enforces scope and tenant context
def key_auth(
required_scope: str,
current_key: Dict = Depends(get_current_key),
):
if required_scope not in current_key.get("scopes", []):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Insufficient scope",
)
return current_key
@app.get("/tenants/{tenant_id}/invoices")
def list_invoices(
tenant_id: str,
key_info: Dict = Depends(lambda: key_auth("read:invoices")),
):
# Enforce tenant isolation: the key’s tenant_id must match the path tenant_id
if key_info["tenant_id"] != tenant_id:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Access to this tenant is denied",
)
# Here, query the database for invoices belonging to tenant_id
# db.query(Invoice).filter(Invoice.tenant_id == tenant_id)
return {"tenant_id": tenant_id, "invoices": []}
This pattern ensures that even with a valid API key, each request re-validates the relationship between the key and the resource identifier. It prevents BOLA by requiring that the path parameter tenant_id matches the key’s metadata, and it prevents privilege escalation by checking scopes before allowing write actions.
For production, replace the in-memory API_KEYS dictionary with a secure, low-latency lookup (e.g., a cached database or secret manager), and ensure keys are rotated periodically. Combine this with rate limiting and audit logging to further reduce risk. The middleBrick Pro plan supports continuous monitoring and CI/CD integration, so you can fail builds if new endpoints lack these per-resource checks.