Home/Learn/FastAPI/Headers, Cookies & the Request Object

Headers, Cookies & the Request Object

Intermediate
Requests & Validation

Header() and Cookie() bind request metadata as typed parameters (X-API-Key becomes x_api_key); Response.set_cookie sends session cookies with the security flags that interviews love; Request is the escape hatch.

Overview

Beyond the body and URL, requests carry metadata: headers (auth tokens, API keys, trace IDs, client info) and cookies (sessions, preferences). FastAPI binds both as typed parameters — Header() auto-converts underscores to hyphens, so x_request_id reads X-Request-ID — keeping them validated and documented like everything else. On the way out, response.set_cookie writes cookies, and the trio of flags (httponly, secure, samesite) is the difference between a session cookie and an XSS-stealable one. When you need the raw request — client IP behind a proxy, the exact URL, an unusual body — the Request object is the escape hatch, and X-Forwarded-For handling is its classic gotcha. This chapter is also the quiet foundation for the Auth category later.

Reading Headers and Cookies as Parameters

Header() and Cookie() are Query()'s siblings for metadata. Python names map to header names (x_api_key → X-Api-Key). Defaults make them optional; missing required ones produce the standard 422.

Typed, documented access to metadata — no request.headers digging
from fastapi import FastAPI, Header, Cookie, HTTPException

app = FastAPI()

@app.get("/profile")
def profile(
    x_api_key: str = Header(),                     # required: X-Api-Key
    user_agent: str | None = Header(default=None), # standard header, optional
    x_request_id: str | None = Header(default=None),   # tracing id from gateway
    session_id: str | None = Cookie(default=None),     # cookie by name
):
    if x_api_key != "tophub-secret-1":             # (real auth → Auth chapters)
        raise HTTPException(401, "invalid API key")
    return {
        "client": user_agent,
        "trace": x_request_id,
        "has_session": session_id is not None,
    }

# curl http://127.0.0.1:8000/profile \
#      -H "X-Api-Key: tophub-secret-1" \
#      -H "X-Request-ID: req-7f3a" \
#      --cookie "session_id=abc123"

# Notes:
#   - underscore ↔ hyphen conversion is automatic (convert_underscores=True)
#   - headers are case-insensitive per HTTP spec
#   - Authorization: Bearer <token> is usually read via Security utilities
#     (OAuth2PasswordBearer) rather than raw Header() — coming in Auth

Setting Cookies + the Raw Request

response.set_cookie sends Set-Cookie with security flags: httponly blocks JavaScript access (XSS), secure restricts to HTTPS, samesite curbs CSRF. Request exposes url, method, headers, and client — with the proxy caveat on IPs.

httponly + secure + samesite on every session cookie — non-negotiable
from fastapi import FastAPI, Request, Response

app = FastAPI()

@app.post("/login")
def login(response: Response):                 # declare Response to decorate it
    response.set_cookie(
        key="session_id",
        value="sess-91f2c7",                   # (signed/random in real life)
        max_age=60 * 60 * 12,                  # 12 hours
        httponly=True,    # JS cannot read it  → XSS cannot steal the session
        secure=True,      # HTTPS only         → no leaking on plain HTTP
        samesite="lax",   # not sent on cross-site POSTs → CSRF protection
    )
    return {"status": "logged in"}

@app.post("/logout")
def logout(response: Response):
    response.delete_cookie("session_id")
    return {"status": "logged out"}

@app.get("/whoami")
def whoami(request: Request):                  # the escape hatch
    # Behind nginx/ALB, request.client.host is the PROXY's IP.
    # The real client rides in X-Forwarded-For: "client, proxy1, proxy2"
    fwd = request.headers.get("x-forwarded-for")
    real_ip = fwd.split(",")[0].strip() if fwd else request.client.host
    return {
        "ip": real_ip,
        "method": request.method,
        "path": str(request.url.path),
    }
# Caveat: X-Forwarded-For is spoofable unless a trusted proxy strips/sets it —
# only trust it when YOUR infrastructure is the one writing it.

Key Points to Remember

  • 1Header()/Cookie() bind metadata as typed params; x_api_key ↔ X-Api-Key mapping is automatic
  • 2set_cookie with httponly + secure + samesite="lax" is the baseline session recipe
  • 3Request is the escape hatch: url, method, headers, client
  • 4Behind a proxy, the client IP is in X-Forwarded-For — and only trustworthy from your own proxy

Interview Questions

Sign in to ask Aria
1

What do httponly, secure, and samesite each protect a session cookie from?

MediumPhonePe
2

Your FastAPI service is behind nginx — why does request.client.host show one IP for everyone, and what is the fix?

MediumHotstar
3

Design request tracing: where does X-Request-ID come from and how does it flow through services?

HardUber

Ask Aria about Headers, Cookies & the Request Object

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…