Bola Idor in Fastapi with Jwt Tokens
Bola Idor in Fastapi with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Broken Object Level Authorization (BOLA) occurs when an API fails to enforce proper ownership or authorization checks between objects that share the same access pattern. In FastAPI applications that use JWT tokens for authentication, BOLA can emerge when endpoints validate the token but do not verify that the resource being accessed belongs to the authenticated subject encoded in the token.
Consider a typical setup: JWTs contain a subject claim (sub) identifying the user. FastAPI routes use an OAuth2PasswordBearer dependency to extract the token, decode it with a public key or secret, and attach the current user to the request state. This establishes identity, but does not by itself enforce object-level permissions. If a route like /users/{user_id} or /accounts/{account_id} uses the token only to confirm the caller is authenticated, an attacker can manipulate the ID parameter to access or modify another user’s data. Because the authorization check is missing, the API returns or accepts changes to resources the subject should not be able to touch.
In practice, this often maps to the OWASP API Top 10 category Broken Object Level Authorization and may align with relevant compliance frameworks such as PCI-DSS and SOC2. JWT tokens provide strong authentication, but they do not prevent horizontal privilege escalation when object ownership is not validated on each request. Attackers can enumerate predictable numeric or UUID identifiers, or even guess references that map to other users, accounts, or sensitive records. Without explicit checks tying the resource identifier to the subject in the token, FastAPI will happily serve or update the data, creating a BOLA vulnerability.
Real-world examples include endpoints that expose account details, transaction records, or configuration objects where the ID in the path should be cross-referenced with the subject in the JWT. Even with rate limiting and input validation in place, missing ownership checks allow unauthorized read and write operations. The risk is compounded when responses include sensitive fields or when write operations do not validate that the subject has rights to perform the action on the target object.
Jwt Tokens-Specific Remediation in Fastapi — concrete code fixes
Remediation requires coupling JWT-based authentication with explicit object ownership checks at the business logic layer. Authentication verifies identity; authorization must verify rights to the specific resource. Below are concrete, working FastAPI snippets that demonstrate the pattern.
Authentication with JWT and dependency injection
Decode the JWT in a dependency, attach the subject and scopes to the request state, and make them available to routes. This example uses PyJWT with an RSA public key.
import jwt
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
app = FastAPI()
security = HTTPBearer()
def decode_jwt_token(credentials: HTTPAuthorizationCredentials = Depends(security)) -
> dict:
token = credentials.credentials
try:
payload = jwt.decode(
token,
key="-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n-----END PUBLIC KEY-----\n",
algorithms=["RS256"],
options={"require": ["exp", "sub", "iat"]},
)
return payload
except jwt.ExpiredSignatureError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token expired",
headers={"WWW-Authenticate": "Bearer"},
)
except jwt.InvalidTokenError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token",
headers={"WWW-Authenticate": "Bearer"},
)
async def get_current_subject(payload: dict = Depends(decode_jwt_token)) -
> str:
return payload.get("sub")
Enforcing object ownership in a route
After authentication, explicitly check that the requested object belongs to the subject. Use a service function that validates ownership before proceeding.
from fastapi import APIRouter, Depends
from pydantic import BaseModel
router = APIRouter()
class Account(BaseModel):
id: str
subject: str
balance: float
# Simulated data access layer
def get_account_from_db(account_id: str) -
> Account | None:
# Replace with actual DB query
return Account(id=account_id, subject="user-123", balance=100.0)
def verify_ownership(account: Account, subject: str) -
> None:
if account.subject != subject:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You do not have permission to access this resource",
)
@router.get("/accounts/{account_id}")
async def read_account(
account_id: str,
subject: str = Depends(get_current_subject),
):
account = get_account_from_db(account_id)
if account is None:
raise HTTPException(status_code=404, detail="Account not found")
verify_ownership(account, subject)
return {"id": account.id, "balance": account.balance}
Using scopes and roles for vertical authorization
For endpoints that require elevated privileges, validate scopes or roles encoded in the JWT alongside ownership checks. This pattern prevents both horizontal and vertical BOLA.
def require_scope(required: str):
def check_scope(payload: dict = Depends(decode_jwt_token)) -
> str:
scopes = payload.get("scope", "").split()
if required not in scopes:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"Missing scope: {required}",
)
return payload["sub"]
return check_scope
@router.delete("/accounts/{account_id}")
async def delete_account(
account_id: str,
subject: str = Depends(require_scope("accounts:delete")),
):
account = get_account_from_db(account_id)
if account is None or account.subject != subject:
raise HTTPException(status_code=404, detail="Not found or forbidden")
# Proceed with deletion
return {"status": "deleted"}
These patterns ensure that JWT tokens provide identity while explicit checks enforce object-level authorization. They map to compliance frameworks and reduce the risk of BOLA even when identifiers are predictable.
Related CWEs: bolaAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-250 | Execution with Unnecessary Privileges | HIGH |
| CWE-639 | Insecure Direct Object Reference | CRITICAL |
| CWE-732 | Incorrect Permission Assignment | HIGH |