HIGH cache poisoningfastapijwt tokens

Cache Poisoning in Fastapi with Jwt Tokens

Cache Poisoning in Fastapi with Jwt Tokens — how this specific combination creates or exposes the vulnerability

Cache poisoning occurs when an attacker causes a cache to store malicious content, leading other users to receive that content. In Fastapi applications that use JWT tokens for authorization, a common scenario arises when responses are cached based only on the request path while ignoring variations introduced by authorization headers or token claims. If a cached response is generated for one user’s JWT and then served to another user, sensitive data or elevated permissions may be unintentionally exposed.

Consider a Fastapi endpoint that returns user-specific data and uses bearer JWT tokens for access control. If the caching layer (e.g., a reverse proxy or CDN) caches responses by URL alone, two different requests with different Authorization headers containing distinct JWTs may result in the same cache key. The first request with a privileged token might populate the cache, and a subsequent request with a less privileged token could receive the cached, more privileged response, bypassing intended authorization checks. This violates the principle that access to resources must be enforced per request and not cached in a way that leaks data across users.

Additionally, query parameters that should influence authorization or data scope may not be included in the cache key, compounding the risk. For example, an endpoint accepting user_id as a query parameter might cache a response for one user and incorrectly serve it to another authenticated user with a different JWT claiming a different user identity. Such misconfigurations can lead to Insecure Direct Object References (IDOR) or privilege escalation when cached data is served outside the intended security context.

The interaction between JWT tokens and caching also introduces risks if sensitive data is included in the token payload and that token is accepted as sufficient to determine cacheability. JWTs are often used to carry roles or scopes, and if caching logic mistakenly treats these claims as static across users, the application may fail to enforce fine-grained authorization at the cache layer. This can result in sensitive endpoints being served to users who should not have access, even though the endpoint itself enforces checks at runtime.

To mitigate these issues, ensure that cache keys incorporate elements that differentiate both the request path and the authorized identity, such as a normalized subject identifier from the JWT rather than the entire token. Avoid caching responses that vary based on authorization unless the cache explicitly respects token claims and user context. Validate that no sensitive data is embedded in cacheable representations and apply appropriate no-store directives for private or user-specific data.

Jwt Tokens-Specific Remediation in Fastapi — concrete code fixes

Remediation focuses on ensuring cache keys account for user-specific claims within JWT tokens and that responses are not cached in a way that leaks data across identities. Below are concrete code examples for a Fastapi application that uses JWT-based authentication and demonstrates secure practices.

First, decode the JWT to extract claims and derive a user-specific cache key component. Use a library like python-jose to validate and parse the token without relying on untrusted payload data.

from fastapi import FastAPI, Depends, HTTPException, Header
from jose import jwt, JWTError
from pydantic import BaseModel
from typing import Optional

app = FastAPI()

SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"

class Item(BaseModel):
    id: int
    name: str
    owner_id: str

def get_current_user(authorization: str = Header(...)):
    if not authorization.startswith("Bearer "):
        raise HTTPException(status_code=401, detail="Invalid authentication scheme")
    token = authorization.split(" ")[1]
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        user_id: str = payload.get("sub")
        if user_id is None:
            raise HTTPException(status_code=401, detail="Invalid token claims")
        return {"user_id": user_id}
    except JWTError:
        raise HTTPException(status_code=401, detail="Invalid token")

@app.get("/items/{item_id}")
def read_item(item_id: int, user=Depends(get_current_user)):
    # item_id and user["user_id"] should be used to construct cache key
    return {"item_id": item_id, "owner_id": user["user_id"]}

When integrating caching, include the user identifier in the cache key to prevent cross-user contamination. For example, if using a server-side cache, structure the key to combine the route, query parameters, and a normalized user ID extracted from the JWT.

# Example cache key construction (pseudocode for cache layer)
cache_key = f"resource:item:{item_id}:user:{user_id}"

Ensure that responses containing user-specific data are marked as private or not cacheable when appropriate. Use HTTP headers to instruct caches not to store sensitive responses.

from fastapi import Response

@app.get("/users/me")
def get_current_user_info(user=Depends(get_current_user), response: Response = None):
    response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, private"
    response.headers["Pragma"] = "no-cache"
    return {"user_id": user["user_id"]}

For public but user-context-dependent data, validate that the cache respects query parameters and JWT-derived claims. Avoid caching tokens or any data that could be reused across different authorization contexts. Rotate secrets and validate token audiences to reduce the impact of leaked tokens.

Frequently Asked Questions

How can I ensure my cache keys differentiate users with different JWTs?
Include a normalized user identifier (such as the 'sub' claim) from the JWT in your cache key, and avoid caching responses based solely on the request path or query parameters that do not reflect user context.
Should I cache responses that contain sensitive claims from JWT tokens?
No. Avoid caching responses that include or depend on sensitive JWT claims. Use Cache-Control: no-store for private data and ensure cache entries are scoped to the authorized user.