Mass Assignment in Fastapi with Jwt Tokens
Mass Assignment in Fastapi with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Mass assignment in FastAPI occurs when a client-supplied payload is mapped directly to a model or function parameters without explicit field filtering. When JWT tokens are used for authentication, the presence of trusted identity information can create a false sense of safety that leads developers to skip authorization checks on sensitive operations. The token may indicate a user role or identity, but if route handlers rely only on the token for authorization while accepting unvalidated input for object creation or updates, the API remains vulnerable to privilege escalation or unintended data modification.
For example, a FastAPI endpoint that creates a user profile might trust the authenticated identity from the JWT to set the owner ID, while also accepting request body fields that can arbitrarily set administrative flags or relationships. If the Pydantic model includes fields that are not explicitly excluded or declared as optional with defaults, an attacker can inject values for is_admin, role, or parent_id even when the endpoint is protected by JWT validation. This is a BOLA/IDOR pattern enabled by mass assignment, where ownership is assumed from the token but not enforced on the payload.
In FastAPI, this often arises when developers use a single Pydantic model for both input and database mapping, or when they rely on dict(model) or model.dict() to persist data without excluding sensitive keys. Because JWT tokens are typically verified before the body is parsed, the developer may incorrectly consider the request fully authenticated and authorized, overlooking that the request body can still modify critical attributes. The attack surface is the unauthenticated (black-box) test surface that middleBrick scans, which includes parameter tampering and property authorization checks that detect whether mass assignment bypasses intended access controls.
OpenAPI/Swagger spec analysis helps expose these risks by comparing the declared request schema with the implementation, especially when $ref definitions are used across endpoints. If the spec defines a model with privileged fields but the route does not validate or filter them, the scan can identify missing authorization on specific properties. This aligns with OWASP API Top 10 A01:2023 broken object level authorization, where mass assignment is a common implementation flaw that enables unauthorized changes to object properties.
Jwt Tokens-Specific Remediation in Fastapi — concrete code fixes
Remediation centers on explicit allowlists and separation of concerns: authenticate with JWT, then validate and filter input independently. Do not rely on the token to convey or imply permissions for mutable fields. Use Pydantic models to exclude sensitive fields from input, and enforce authorization at the handler level based on the authenticated identity, not on assumptions derived from the token payload.
Below are concrete code examples for FastAPI that demonstrate secure handling when JWT tokens are in use.
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from pydantic import BaseModel, Field
from typing import Optional
import jwt
app = FastAPI()
security = HTTPBearer()
# Secret and algorithm should be configured securely
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
class TokenPayload(BaseModel):
sub: str # subject, typically user ID
role: str # e.g., 'user', 'admin'
def decode_token(credentials: HTTPAuthorizationCredentials = Depends(security)) -> TokenPayload:
try:
payload = jwt.decode(credentials.credentials, SECRET_KEY, algorithms=[ALGORITHM])
return TokenPayload(sub=payload.get("sub"), role=payload.get("role"))
except jwt.PyJWTError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)
# Input model that explicitly excludes sensitive fields
class UserCreateInput(BaseModel):
username: str = Field(..., min_length=1, max_length=64)
email: str = Field(..., regex=r"^[^\s@]+@[^\s@]+\.[^\s@]+$")
# is_admin and role are not accepted from client; handled server-side
# Pydantic model for database mapping; sensitive fields have defaults
class UserRecord(BaseModel):
id: int
username: str
email: str
role: str = "user" # default; not settable by client
is_admin: bool = False
@app.post("/users")
async def create_user(
body: UserCreateInput,
token_info: TokenPayload = Depends(decode_token)
):
# Authorization can use token_info.role if needed, but creation is role-agnostic here
# Map only allowed fields; sensitive fields are assigned server-side
user = UserRecord(
id=0, # assigned by DB
username=body.username,
email=body.email,
role=token_info.role if token_info.role in {"admin", "user"} else "user",
is_admin=False, # never set via mass assignment
)
# Persist user to database (implementation omitted)
return {"id": user.id, "username": user.username, "role": user.role, "is_admin": user.is_admin}
Key practices:
- Separate authentication (JWT verification) from input validation; do not merge them into a single model.
- Define distinct input models that omit privileged fields, and separate persistence models that set defaults server-side.
- Use Pydantic’s
excludeor explicit field omission when serializing to ensure sensitive keys are never populated from request data. - Enforce authorization checks based on the authenticated identity from the token, not on fields supplied by the client.
middleBrick scans can validate these patterns by checking whether request schemas include sensitive fields and whether runtime tests attempt to modify them without authorization. The scanner’s BOLA/IDOR and Property Authorization checks are designed to surface these issues, providing prioritized findings and remediation guidance that map to frameworks such as OWASP API Top 10.
Related CWEs: propertyAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-915 | Mass Assignment | HIGH |