Broken Function Level Authorization

Broken Function Level Authorization in FastAPI [May 2026] [GHSA-v6qf-75pr-p96m]

[Updated May 2026] Updated GHSA-v6qf-75pr-p96m

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)

CVE References

Choose which optional cookies to allow. You can change this any time.