Overview
Broken Function Level Authorization (BFLA) occurs when an API requires authentication but does not enforce fine-grained access controls for individual resources or operations. In FastAPI apps that expose per-resource data (profiles, documents, orders), an attacker could access data they should not see by enumerating identifiers or manipulating inputs.
In FastAPI, this vulnerability commonly appears when a route fetches a resource by an identifier after validating the user, and returns the resource without confirming that the current user has permission to access it. Even if you attach an authentication dependency, authorization checks may be too coarse or scoped only to authentication, not to the specific resource.
The real-world impact includes data leakage, unauthorized edits or deletions, and a broader attack surface as attackers enumerate IDs or exploit patterns. Without object-level checks, function-level boundaries can be bypassed, letting a user perform actions they should not be allowed to perform.
Remediation patterns include centralizing per-resource authorization, failing closed by default, and ensuring every route validates explicit permissions against the resource. In FastAPI, implement a dedicated authorization dependency or a service layer that enforces resource-based policies, and ensure ORM/database queries enforce the same constraints. Add thorough tests and audit logs to detect improper access.
Code Fix Example
FastAPI API Security Remediation
from fastapi import FastAPI, Depends, HTTPException
from pydantic import BaseModel
app = FastAPI()
class User(BaseModel):
id: int
role: str # 'admin' or 'user'
# Fake in-memory store
_ITEMS = {
1: {"owner_id": 1, "data": "Top secret A"},
2: {"owner_id": 2, "data": "Top secret B"},
}
def get_current_user() -> User:
# In production, extract from bearer token / OAuth2
return User(id=1, role="user")
def _get_item(item_id: int):
item = _ITEMS.get(item_id)
if item is None:
raise HTTPException(status_code=404, detail="Item not found")
return item
# Vulnerable endpoint: no resource-level authorization
@app.get("/vuln/items/{item_id}")
def read_item_vuln(item_id: int, current_user: User = Depends(get_current_user)):
item = _get_item(item_id)
return {"item_id": item_id, "owner_id": item["owner_id"], "data": item["data"]}
# Fixed endpoint: explicit resource-level authorization
def _has_access(user: User, item: dict) -> bool:
return user.id == item["owner_id"] or user.role == "admin"
@app.get("/secure/items/{item_id}")
def read_item_secure(item_id: int, current_user: User = Depends(get_current_user)):
item = _get_item(item_id)
if not _has_access(current_user, item):
raise HTTPException(status_code=403, detail="Forbidden")
return {"item_id": item_id, "owner_id": item["owner_id"], "data": item["data"]}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)