Time Of Check Time Of Use in Fastapi with Basic Auth
Time Of Check Time Of Use in Fastapi with Basic Auth — how this specific combination creates or exposes the vulnerability
Time Of Check Time Of Use (TOCTOU) occurs when the outcome of a security decision depends on the timing between a check and the use of the resource. In FastAPI with HTTP Basic Auth, a typical pattern is to validate credentials on each request and then use the authenticated identity (e.g., username or scopes) to authorize an operation. TOCTOU arises when the data used for authorization changes between the check and the use, often because the check is performed against a stale or mutable source (such as a database row or file) while the actual resource is accessed with a different state. This can expose endpoints where an attacker can alter permissions or identity between validation and action.
Consider a FastAPI endpoint that first verifies a user’s role via Basic Auth and then performs an operation such as updating or deleting a resource. If the role is checked by querying a user record at the start of the request, and later the endpoint modifies the same record or relies on that role to decide what to allow, an attacker who can change the user’s role between the check and the use can escalate privileges or access unauthorized data. With Basic Auth, credentials are sent on each request, but the server must avoid treating the initial successful authentication as a permanent authorization token. For example, if the endpoint uses a cached user object or a database value that can be updated by another request or an administrative action, the authorization decision becomes inconsistent across the request lifecycle.
An illustrative vulnerable pattern in FastAPI might look like this: a dependency that decodes Basic Auth, fetches a user from a repository, and returns a user context; then an endpoint uses that context to decide whether to proceed. If the repository is mutable and the endpoint later performs an action using the same user identifier without revalidating critical attributes (such as role or ownership), the authorization can be bypassed. This is especially risky when combined with endpoints that accept user-supplied identifiers for targets (such as a user ID or resource ID) and perform a check like "does this resource belong to the user?" before operating on it. An attacker who can modify the backend data between the check and the use can manipulate the outcome, leading to Insecure Direct Object References (IDOR) or BOLA-style issues.
Middleware or filters that set request state based on credentials can also introduce TOCTOU if the state is not re-evaluated at the point of use. For example, if a dependency attaches a user object to the request state after a Basic Auth check, and later business logic reads that state to make authorization decisions, any asynchronous processing or background task that uses a snapshot of that state can operate on outdated information. With Basic Auth, the credentials are present per request, but FastAPI’s dependency system should ensure that any authorization-sensitive checks are performed as close as possible to the operation, and that shared state is not relied upon across asynchronous boundaries or mutable data.
To mitigate this specific to FastAPI with Basic Auth, favor re-validation at the point of use, avoid caching authorization-critical attributes across the request lifecycle, and design endpoints to be idempotent with respect to permissions. Where possible, bind the authorization decision to the same access pattern as the operation, using parameters and object ownership checks that are evaluated within the same transactional or logical boundary, rather than relying on earlier authentication steps alone.
Basic Auth-Specific Remediation in Fastapi — concrete code fixes
Secure FastAPI applications using HTTP Basic Auth should validate credentials on every request and perform authorization checks immediately before the operation, avoiding shared mutable state between check and use. Below are concrete, working examples that demonstrate a safe pattern with explicit re-validation and minimal shared state.
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from pydantic import BaseModel
from typing import Dict
import secrets
app = FastAPI()
security = HTTPBasic()
# In-memory store for example; in production, use a secure, transactional data source
users_db: Dict[str, dict] = {
"alice": {"password": secrets.token_hex(16), "role": "admin"},
"bob": {"password": secrets.token_hex(16), "role": "user"},
}
class Item(BaseModel):
id: str
owner: str
data: str
items: Dict[str, Item] = {}
def get_user_role(username: str, credentials: HTTPBasicCredentials) -> str:
"""Re-validate credentials and fetch role at the point of authorization."""
user = users_db.get(username)
if not user or user["password"] != credentials.password:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid credentials",
headers={"WWW-Authenticate": "Basic"},
)
return user["role"]
def get_current_user(credentials: HTTPBasicCredentials = Depends(security)):
"""Decode credentials; do not perform authorization here."""
# In a real app, decode from header; this example uses a helper to simulate provisioned credentials.
# For illustration, we expect username to be provided via a test header or environment in this snippet.
# Actual Basic Auth: decode from credentials.username/credentials.password.
return {"username": credentials.username, "password": credentials.password}
@app.post("/items/")
def create_item(item: Item, user: dict = Depends(get_current_user)):
# Re-validate and authorize immediately before use
role = get_user_role(user["username"], user)
if role != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions")
items[item.id] = item
return {"status": "created", "id": item.id}
@app.delete("/items/{item_id}")
def delete_item(item_id: str, user: dict = Depends(get_current_user)):
# Re-validate and authorize immediately before use
role = get_user_role(user["username"], user)
if role != "admin":
raise HTTPException(status_code=403, detail="Insufficient permissions")
if item_id not in items:
raise HTTPException(status_code=404, detail="Item not found")
del items[item_id]
return {"status": "deleted"}
@app.get("/items/{item_id}")
def read_item(item_id: str, user: dict = Depends(get_current_user)):
# Re-validate and authorize immediately before use
role = get_user_role(user["username"], user)
item = items.get(item_id)
if not item:
raise HTTPException(status_code=404, detail="Item not found")
if role != "admin" and item.owner != user["username"]:
raise HTTPException(status_code=403, detail="Access denied")
return item
Key points in this remediation:
- Credentials are decoded once per request via
get_current_user, but authorization (role checks) is performed in each endpoint immediately before the operation usingget_user_role. This ensures the check and use happen with the same up-to-date data. - No authorization decision is cached in request state; each endpoint recomputes the role using the current credentials and backend store, avoiding TOCTOU between authentication and authorization.
- For operations that depend on resource ownership (e.g., delete or update), the endpoint re-fetches or validates ownership within the same function, binding the decision to the specific resource identifier supplied in the request.
- If you use a persistent store, ensure that reads and writes are performed within appropriate transaction boundaries to prevent race conditions; the pattern above keeps checks close to the action to reduce the window for inconsistent state.
For production, replace the in-memory dictionary with a secure data source, use HTTPS to protect Basic Auth credentials, and consider additional protections such as rate limiting to reduce brute-force risk. The CLI tool middlebrick can scan endpoints to detect authentication and authorization inconsistencies, while the Pro plan enables continuous monitoring to catch regressions introduced by changes in authorization logic.