HIGH cross site request forgeryfastapijwt tokens

Cross Site Request Forgery in Fastapi with Jwt Tokens

Cross Site Request Forgery in Fastapi with Jwt Tokens — how this specific combination creates or exposes the vulnerability

Cross Site Request Forgery (CSRF) is a web security vulnerability that tricks a victim into executing unwanted actions on a web application in which they are authenticated. When using FastAPI with JWT tokens for authentication, CSRF risks can emerge depending on how tokens are stored and sent by clients. JWTs are commonly held in browser storage (localStorage or sessionStorage) and attached to requests via JavaScript, which makes them accessible to malicious scripts and susceptible to CSRF if anti-CSRF measures are not implemented.

In a typical FastAPI setup, a frontend obtains a JWT after login and stores it in localStorage. Subsequent API calls attach the token via an Authorization header (Bearer scheme). Because browsers automatically include cookies with cross-origin requests to the same domain, an attacker can craft a malicious site that sends requests to your FastAPI endpoints. If the frontend relies solely on cookies for session management alongside JWTs, or if the API is designed to accept both cookie-based and header-based authentication, the browser may send authentication state automatically without the JavaScript explicitly attaching the JWT. This mismatch can allow a forged request from a malicious site to succeed, leading to unauthorized actions such as changing email or password.

CSRF against JWT-authenticated FastAPI endpoints is more likely when developers inadvertently enable cookie-based session handling or allow credentials on cross-origin requests. For example, if CORS is configured with allow_credentials=True and origins are too permissive, browsers may include cookies in requests initiated by malicious third-party sites. Even if the backend validates the JWT from the Authorization header, the presence of session cookies can create a scenario where the request appears authenticated. Attack vectors such as form submissions or image tags with GET requests that trigger state-changing POST methods can exploit this if proper CSRF protections are absent.

The risk is compounded when token storage is not aligned with request transmission patterns. Storing JWTs in cookies without the Secure, HttpOnly, and SameSite attributes increases exposure to both CSRF and XSS. FastAPI applications that do not implement synchronizer token patterns, double-submit cookie checks, or anti-CSRF headers are vulnerable. Attackers can leverage social engineering or malicious sites to induce authenticated requests that the server processes as legitimate, especially when endpoints lack idempotency keys or explicit origin verification.

To detect such issues, scanning tools evaluate whether FastAPI endpoints rely on cookies for authentication without enforcing CSRF tokens or validating the Origin header. They also check CORS configurations for overly permissive settings and inspect whether tokens are exposed to JavaScript via insecure storage. Understanding the interaction between JWT usage and browser behavior is essential for mitigating CSRF in FastAPI APIs.

Jwt Tokens-Specific Remediation in Fastapi — concrete code fixes

Remediation focuses on ensuring that JWTs are handled in a way that prevents browsers from automatically including them in cross-origin requests. The most effective approach is to avoid storing JWTs in cookies unless they are protected with Secure, HttpOnly, and SameSite=Strict or Lax. Instead, keep JWTs in memory or in isolated storage and explicitly set the Authorization header for API calls.

For APIs consumed by browser-based JavaScript, enforce strict CORS policies and disable credentials unless absolutely necessary. Below is a secure FastAPI configuration that demonstrates these principles:

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from fastapi.middleware.cors import CORSMiddleware
import jwt
from datetime import datetime, timedelta

app = FastAPI()

security = HTTPBearer()

origins = [
    "https://your-trusted-frontend.com",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=False,
    allow_methods=["GET", "POST", "PUT", "DELETE"],
    allow_headers=["Authorization", "Content-Type"],
    expose_headers=["X-Request-ID"],
)

def create_jwt(data: dict, expires_delta: timedelta = timedelta(minutes=15)):
    to_encode = data.copy()
    expire = datetime.utcnow() + expires_delta
    to_encode.update({"exp": expire})
    token = jwt.encode(to_encode, "your-secret-key", algorithm="HS256")
    return token

async def get_current_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
    token = credentials.credentials
    try:
    payload = jwt.decode(token, "your-secret-key", algorithms=["HS256"])
    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"},
    )

@app.post("/transfer")
async def transfer_funds(payload: dict, token_data: dict = Depends(get_current_token)):
    # Business logic here
    return {"status": "ok"}

This configuration ensures that CORS does not allow credentials, preventing browsers from sending cookies or authentication headers to untrusted origins. JWTs are expected in the Authorization header only, and the server does not rely on cookies for authentication state.

For applications that must support browser-based token storage, implement anti-CSRF tokens or double-submit cookie patterns. Set the JWT in an HttpOnly cookie with Secure and SameSite=Strict, and require a matching custom header (e.g., X-CSRF-Token) for state-changing requests. This ensures that cross-origin requests cannot satisfy both the cookie and the header requirement simultaneously.

Example of a protected cookie-based approach:

from fastapi import Response
from fastapi.responses import JSONResponse

@app.post("/login")
async def login(response: Response):
    token = create_jwt({"sub": "user123"})
    response.set_cookie(
        key="auth_token",
        value=token,
        httponly=True,
        secure=True,
        samesite="strict",
    )
    return {"message": "logged in"}

By combining strict CORS, secure cookie attributes, and explicit header-based authorization for sensitive operations, FastAPI applications can effectively mitigate CSRF risks while still leveraging JWTs for stateless authentication.

Frequently Asked Questions

Can storing JWTs in localStorage still be safe with FastAPI if I use strict CORS?
Strict CORS reduces risk but does not fully prevent CSRF when JWTs are in localStorage, because malicious JavaScript on an allowed origin can still read the token and forge requests. Prefer HttpOnly cookies with SameSite attributes and avoid relying on CORS alone.
What should I do if my FastAPI API is consumed by a browser frontend and I need to use cookies for the JWT?
Set the JWT in an HttpOnly, Secure, SameSite=Strict cookie, disable credentials in CORS, and implement an anti-CSRF header or double-submit pattern for state-changing requests to ensure cross-origin requests cannot trigger authenticated actions.