HIGH cross site request forgeryfastapihmac signatures

Cross Site Request Forgery in Fastapi with Hmac Signatures

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

Cross-site request forgery (CSRF) remains relevant for APIs that rely on cookies for session management, even when FastAPI services use HMAC signatures to authenticate requests. CSRF attacks occur when a victim’s browser is made to execute unwanted actions on a trusted domain where the user is authenticated via session cookies. In FastAPI applications that serve web clients (e.g., JavaScript frontends), cookies are often used for session state, while HMAC signatures are applied to specific API payloads or headers to verify integrity and origin. The vulnerability arises when HMAC validation is applied only to a subset of requests or only to a specific header or body field, leaving other endpoints or methods unprotected.

Consider a FastAPI service that uses an HMAC signature in a custom header, such as x-api-signature, to verify that the request body has not been tampered with. If the application does not enforce the same signature verification for state-changing POST, PUT, or DELETE requests initiated from a browser context, an attacker can craft a form on a malicious site that submits to the target endpoint using the victim’s existing cookie-based session. The browser automatically includes the session cookie, and if the endpoint relies solely on cookie-based authentication without requiring the HMAC for that route, the request may be processed as legitimate. This mismatch between signature coverage and session handling creates a CSRF surface: the HMAC protects data integrity but does not protect the authentication/session state that the browser manages via cookies.

Additionally, if the HMAC is computed over only part of the request (e.g., the JSON body) and the application does not bind the signature to critical headers or to the presence of anti-CSRF tokens in cookies, attackers can exploit predictable or missing coverage. For example, unsafe CORS configurations that allow credentials can further expose the endpoint to CSRF when origins are too permissive. The interplay between HMAC-based integrity checks and cookie-based sessions in FastAPI can inadvertently leave authentication boundaries inconsistent, enabling CSRF-style abuse where an attacker forges a signed request pattern but relies on the victim’s browser to supply session context the server trusts.

In practice, this means developers might mistakenly believe that HMAC signatures alone prevent CSRF, when in fact CSRF mitigation requires explicit handling of authentication state and request context. Without synchronizing cookie protections (such as SameSite attributes) and ensuring that HMAC verification applies to all state-changing operations, the attack surface remains open. The scanner checks for such inconsistencies by correlating authentication mechanisms with CSRF indicators and flagging unprotected state-changing routes.

Hmac Signatures-Specific Remediation in Fastapi — concrete code fixes

To remediate CSRF risks when using HMAC signatures in FastAPI, ensure that HMAC verification is applied consistently to all state-changing endpoints and that cookie-based session handling is hardened. Below are concrete code examples that demonstrate a secure pattern.

Example 1: HMAC verification middleware that validates a signature header for all relevant routes

import hmac
import hashlib
from fastapi import FastAPI, Request, Depends, HTTPException, status
from fastapi.middleware import Middleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware

app = FastAPI()

# In production, store this securely and rotate periodically
SECRET_KEY = b"super-secret-key-used-for-hmac"

def verify_hmac_signature(request: Request) -> bool:
    """Verify HMAC signature over the request body and selected headers."""
    signature_header = request.headers.get("x-api-signature")
    if signature_header is None:
        return False
    # Use a fixed set of headers to include in the signature to prevent header smuggling
    payload = request.method.encode() + b":" + request.url.path.encode()
    # Include body only for methods that have a body
    if request.method in ("POST", "PUT", "PATCH"):
        body = request.body()
        # Re-create body for downstream use by reading and setting again
        request._body = body
        payload += b"|" + body
    expected = hmac.new(SECRET_KEY, payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature_header)

@app.middleware("http")
async def hmac_csrf_middleware(request: Request, call_next):
    if request.method in ("POST", "PUT", "DELETE", "PATCH"):
        if not verify_hmac_signature(request):
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Invalid or missing HMAC signature"
            )
    response = await call_next(request)
    return response

The above middleware validates the HMAC for all state-changing HTTP methods. It signs the method, path, and body to bind the signature to the exact request content and headers. Using hmac.compare_digest mitigates timing attacks.

Example 2: Per-route dependency that enforces HMAC and CSRF-safe cookie attributes

from fastapi import Cookie, Depends
from fastapi.responses import JSONResponse

def hmac_dependency(
    session_id: str = Cookie(None, httponly=True, samesite="lax", secure=True, path="/")
):
    # session_id is protected by SameSite=Lax, Secure, and HttpOnly
    if not session_id:
        raise HTTPException(status_code=401, detail="Session cookie missing")
    # Additional checks can include binding session_id to HMAC context if needed
    return session_id

@app.post("/transfer")
def transfer_funds(
    data: dict,
    session_id: str = Depends(hmac_dependency)
):
    # HMAC verification is enforced via middleware; session cookie attributes reduce CSRF risk
    return JSONResponse(content={"status": "ok"})

In this pattern, the cookie is marked SameSite=Lax (or Strict where appropriate), Secure, and HttpOnly. The HMAC middleware ensures that even if a forged request arrives with a valid session cookie, it will be rejected unless the correct signature is present. For APIs consumed by browser-based clients, also include anti-CSRF tokens in cookies and require them in headers for state-changing operations to further reduce risk.

Example 3: CORS and secure defaults to complement HMAC and cookies

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://trusted-frontend.example.com"],
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE"],
    allow_headers=["Content-Type", "x-api-signature"],
)

Restrict origins tightly and avoid wildcard origins when credentials are enabled. Pair these settings with the HMAC middleware and secure cookie attributes to align integrity, authentication, and CSRF protections.

Frequently Asked Questions

Does using HMAC signatures alone prevent CSRF in FastAPI?
No. HMAC signatures protect request integrity but do not automatically prevent CSRF if authentication relies on cookies and signature validation is not applied consistently to all state-changing endpoints. Combine HMAC verification with SameSite/Secure cookies and anti-CSRF tokens where needed.
What should I include in the HMAC payload to reduce CSRF risk?
Include the HTTP method, request path, and body (for state-changing methods). Binding the signature to these elements ensures that a replay or forged request without the correct signature will be rejected, reducing the window for CSRF-style abuse.