FastAPI applications can be secured by enforcing strict policies on incoming request headers and cookies, preventing common attack vectors like Cross-Site Scripting (XSS) and Cross-Site Request Forgery (CSRF).

Let’s see this in action. Imagine a simple FastAPI endpoint designed to store user preferences:

from fastapi import FastAPI, Request, Response, HTTPException
from fastapi.responses import JSONResponse
import secrets

app = FastAPI()

# In-memory store for demonstration
user_prefs = {}

@app.post("/prefs/{user_id}")
async def set_prefs(user_id: str, request: Request, response: Response):
    if "sessionid" not in request.cookies:
        raise HTTPException(status_code=401, detail="Unauthorized: Missing session cookie")

    session_id = request.cookies["sessionid"]
    if session_id not in user_prefs or user_prefs[session_id] != user_id:
        raise HTTPException(status_code=403, detail="Forbidden: Invalid session")

    # For demonstration, we'll just acknowledge the request.
    # In a real app, you'd parse request.json() or form data to update preferences.
    return JSONResponse(content={"message": f"Preferences updated for {user_id}"})

@app.post("/login")
async def login(request: Request):
    # In a real app, you'd validate username/password
    user_id = "test_user_123" # Assume login successful

    session_id = secrets.token_hex(16)
    user_prefs[session_id] = user_id

    response = JSONResponse(content={"message": "Login successful"})
    response.set_cookie(
        "sessionid",
        session_id,
        httponly=True,
        samesite="Strict",
        secure=True,
        max_age=3600 # 1 hour
    )
    return response

# Example of a protected resource
@app.get("/profile")
async def get_profile(request: Request):
    if "sessionid" not in request.cookies:
        raise HTTPException(status_code=401, detail="Unauthorized: Missing session cookie")

    session_id = request.cookies["sessionid"]
    user_id = user_prefs.get(session_id)

    if not user_id:
        raise HTTPException(status_code=403, detail="Forbidden: Invalid session")

    return JSONResponse(content={"user_id": user_id, "profile_data": "..."})

This example demonstrates setting a cookie with httponly, samesite="Strict", and secure=True. The /prefs and /profile endpoints then check for the presence and validity of this sessionid cookie before allowing access.

The problem this solves is that attackers can often inject malicious scripts into web pages or trick users into performing unwanted actions. Strict header and cookie policies act as a robust defense against these threats.

Internally, the browser enforces these policies at the network level before your FastAPI application even sees the request. httponly prevents JavaScript from accessing the cookie, mitigating XSS attacks where an attacker might try to steal session cookies. samesite="Strict" ensures the cookie is only sent with requests originating from the same site, preventing CSRF attacks where a malicious site could trick a user’s browser into sending their cookies to your legitimate site. secure=True tells the browser to only send the cookie over HTTPS, protecting it from eavesdropping on unsecured networks.

Beyond these cookie attributes, you can also enforce strict header policies. For instance, Content-Security-Policy (CSP) is a powerful header that tells the browser which dynamic resources (like scripts, CSS, images) are allowed to load for a given page. By defining a strict CSP, you can significantly reduce the risk of XSS attacks.

Consider this example of setting a CSP header:

from fastapi import FastAPI, Request, Response
from fastapi.responses import JSONResponse

app = FastAPI()

@app.get("/")
async def read_root(request: Request):
    response = JSONResponse(content={"message": "Hello, World!"})
    response.headers["Content-Security-Policy"] = "default-src 'self'; script-src 'self' trusted-scripts.com; object-src 'none';"
    return response

Here, default-src 'self' means that by default, all content must be served from the same origin as the document. script-src 'self' trusted-scripts.com allows scripts from the same origin and from trusted-scripts.com. object-src 'none' disables plugins like Flash. If an attacker tries to inject a script from an unauthorized domain, the browser, guided by the CSP header, will block it.

Another critical header is X-Content-Type-Options: nosniff. This header prevents the browser from MIME-sniffing a response away from the declared content type. For example, if a user uploads a file named malicious.jpg but the server incorrectly sets the Content-Type header to text/plain, a browser might try to interpret it as HTML and execute it. nosniff prevents this, ensuring the browser respects the declared Content-Type.

The X-Frame-Options header is also vital for preventing clickjacking attacks. Setting it to DENY or SAMEORIGIN instructs the browser not to render your site in an <iframe>, <frame>, or <object> on a different site.

To implement these, you can use middleware in FastAPI. A custom middleware can inspect every incoming request and outgoing response to add or modify headers and cookies.

from fastapi import FastAPI, Request, Response
from starlette.middleware.base import BaseHTTPMiddleware

class StrictSecurityMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        response = await call_next(request)

        # Set security headers for all responses
        response.headers["X-Content-Type-Options"] = "nosniff"
        response.headers["X-Frame-Options"] = "DENY"
        response.headers["Content-Security-Policy"] = "default-src 'self'; script-src 'self'; object-src 'none';"

        # Ensure cookies have secure attributes (already set in login example, but good to reinforce)
        # This middleware primarily adds response headers. Cookie attributes are best set
        # when the cookie is initially created (like in the /login endpoint).

        return response

app = FastAPI()
app.add_middleware(StrictSecurityMiddleware)

# ... (your routes here, e.g., login, profile, prefs)

The most surprising aspect of these security measures is how much they rely on the browser acting as a diligent enforcer of rules you set. You’re not writing complex JavaScript to block malicious scripts; you’re telling the browser, "Hey, only trust these sources for scripts" or "Don’t let other sites embed me." The browser then handles the heavy lifting of inspecting every resource request against your defined policy. This delegation of enforcement to the browser is what makes headers like CSP and cookie attributes like samesite so effective and efficient, as they operate at a lower level than application code.

The next step in securing your FastAPI application is understanding and implementing robust authentication and authorization strategies beyond simple session cookies.

Want structured learning?

Take the full Fastapi course →