HIGH cross site request forgeryfastapimongodb

Cross Site Request Forgery in Fastapi with Mongodb

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

Cross Site Request Forgery (CSRF) in a FastAPI application that uses MongoDB arises when state-changing requests rely on session cookies or bearer tokens without additional proof that the request was intentionally issued by the authenticated user. FastAPI does not enforce anti-CSRF protections by default, and MongoDB’s driver does not provide built-in CSRF defenses; the responsibility is on the application to ensure requests are intentional.

Consider a FastAPI endpoint that updates a user’s email by reading an access_token from an HTTP-only cookie and using the decoded subject (sub) to locate and update a document in MongoDB:

from fastapi import FastAPI, Request, HTTPException
from pymongo import MongoClient
from jose import jwt, JWTError

app = FastAPI()
client = MongoClient("mongodb://localhost:27017")
db = client["myproject"]
users = db["users"]

SECRET = "super-secret-key"

@app.post("/users/email")
async def update_email(request: Request):
    token = request.cookies.get("access_token")
    if not token:
        raise HTTPException(status_code=401, detail="Missing token")
    try:
        payload = jwt.decode(token, SECRET, algorithms=["HS256"])
        user_id = payload.get("sub")
        new_email = request.query_params.get("email")
        if not new_email:
            raise HTTPException(status_code=400, detail="Email required")
        result = users.update_one({"_id": user_id}, {"$set": {"email": new_email}})
        if result.matched_count == 0:
            raise HTTPException(status_code=404, detail="User not found")
        return {"status": "ok"}
    except JWTError:
        raise HTTPException(status_code=401, detail="Invalid token")

If this endpoint is rendered via a browser page (e.g., a settings form), a malicious site can craft a request with an email query parameter that the user’s browser automatically sends along with the HTTP-only cookie. Because FastAPI trusts the cookie and the application trusts the cookie’s subject to identify the target user, the update executes on behalf of the victim. MongoDB’s update operation will apply because the application uses the subject from the token to directly construct the filter {"_id": user_id}, and there is no origin or anti-CSRF check to validate the request source.

Another common pattern that increases exposure is exposing unsafe GET endpoints that perform state changes (e.g., toggling a subscription), which can be triggered by simple image tags or iframes. Even when using token-based authentication (e.g., bearer tokens in Authorization headers), if the frontend embeds these tokens in JavaScript-accessible storage and the API does not require a same-site cookie or an anti-CSRF token in the request body/header, a crafted link can perform unauthorized actions when visited by an authenticated user.

The risk is compounded when the MongoDB deployment is exposed indirectly (e.g., via an aggregation or lookup) and the API reflects user-controlled input into database operations without strict validation. While MongoDB itself does not introduce CSRF, the application’s handling of identity (sub) and the lack of per-request integrity checks create a chain that allows an attacker to induce unwanted database writes.

Mongodb-Specific Remediation in Fastapi — concrete code fixes

To mitigate CSRF in a FastAPI + MongoDB stack, ensure that state-changing operations require an explicit anti-CSRF token in the request body or a custom header, in addition to any cookie-based authentication. Do not rely on the SameSite cookie attribute alone; treat it as a defense-in-depth measure. Below is a hardened example that uses a double-submit cookie pattern: an anti-CSRF token is set as a non-HTTP-only cookie and must also be provided in a request header, which the attacker cannot read or set cross-site.

from fastapi import FastAPI, Request, Cookie, Header, HTTPException
from pymongo import MongoClient
from jose import jwt
import secrets

app = FastAPI()
client = MongoClient("mongodb://localhost:27017")
db = client["myproject"]
users = db["users"]

SECRET = "super-secret-key"

@app.post("/users/email")
async def update_email(
    request: Request,
    anti_csrf: str = Cookie(None),
    x_anti_csrf: str = Header(None),
):
    if not anti_csrf or not x_anti_csrf or anti_csrf != x_anti_csrf:
        raise HTTPException(status_code=403, detail="Invalid CSRF token")

    token = request.cookies.get("access_token")
    if not token:
        raise HTTPException(status_code=401, detail="Missing token")
    try:
        payload = jwt.decode(token, SECRET, algorithms=["HS256"])
        user_id = payload.get("sub")
        new_email = request.query_params.get("email")
        if not new_email:
            raise HTTPException(status_code=400, detail="Email required")
        result = users.update_one({"_id": user_id}, {"$set": {"email": new_email}})
        if result.matched_count == 0:
            raise HTTPException(status_code=404, detail="User not found")
        return {"status": "ok"}
    except JWTError:
        raise HTTPException(status_code=401, detail="Invalid token")

When generating the token, include a CSRF claim (e.g., csrf) and store it in both the JWT payload and a separate cookie so you can optionally validate consistency server-side. Here’s how you might set the token and cookie on login:

from fastapi import Response
from jose import jwt
import secrets

@app.post("/login")
async def login(response: Response):
    csrf = secrets.token_urlsafe(32)
    token = jwt.encode({"sub": "user-123", "csrf": csrf}, SECRET, algorithm="HS256")
    response.set_cookie("access_token", token, httponly=True, samesite="lax", secure=True)
    response.set_cookie("csrf_token", csrf, httponly=False, samesite="strict", secure=True)
    return {"status": "logged in"}

For APIs consumed by SPAs, require a custom header (e.g., x-requested-with or a framework-specific header) that cannot be set cross-origin by simple CORS requests, and validate it server-side. Combine this with strict CORS policies in FastAPI (using fastapi.middleware.cors.CORSMiddleware) to limit origins and methods, and ensure that MongoDB update operations always use the subject from the verified token rather than any client-supplied identifier.

Finally, audit your MongoDB queries to ensure they do not inadvertently reflect untrusted input into filter documents or use expressions that bypass intended access controls. Validate and sanitize all inputs, and prefer parameterized updates over dynamic query construction to reduce the likelihood of logic flaws that could be chained with CSRF.

Frequently Asked Questions

Does FastAPI automatically protect against CSRF if I use JWT tokens in the Authorization header?
No. FastAPI does not add CSRF protection; if your frontend sends bearer tokens in headers and your endpoints perform state changes, you must implement anti-CSRF measures such as same-site cookies, anti-CSRF tokens, or custom headers to prevent cross-origin requests from executing unintended actions.
Is using SameSite cookies sufficient to prevent CSRF when using MongoDB with FastAPI?
SameSite cookies are a defense-in-depth measure and reduce the attack surface, but they are not sufficient alone. Browsers may omit cookies in certain cross-site contexts, and non-browser clients can ignore SameSite. Combine SameSite with anti-CSRF tokens and strict origin validation for robust protection.