Skip to content

Chapter 8 · Cookie and Header Parameters ​

Chapter goals: Learn to read Cookies and headers from requests using Cookie and Header, understand the underscore auto-conversion rule, and master setting Cookies securely in responses.

8.1 Another Pair of "Sibling" Parameter Classes ​

Cookie and Header belong to the same family as Query and Path: they all inherit from the same Param base class, so they support identical default values, validation, and alias mechanisms.

Why explicit declaration is required

If you write only session_id: str = None, FastAPI interprets the parameter as a query parameter. To read from Cookies or headers, you must explicitly specify with Cookie() / Header() — these four classes are factory functions that return special parameter objects when imported from fastapi.

python
from typing import Optional
from fastapi import FastAPI, Cookie, Header

app = FastAPI()


@app.get("/items/")
async def read_items(
    session_id: Optional[str] = Cookie(None),   # Read Cookie named session_id
    user_agent: Optional[str] = Header(None),   # Read User-Agent header
    x_token: Optional[str] = Header(None),      # Read X-Token header
):
    return {"session_id": session_id, "user_agent": user_agent, "x_token": x_token}

Test:

bash
curl http://127.0.0.1:8000/items/ \
     -H "User-Agent: my-app/1.0" -H "X-Token: abc123" \
     -b "session_id=xyz789"

8.2 Underscore Auto-Conversion: convert_underscores ​

HTTP standard header naming uses hyphens (user-agent, content-type), but Python variable names cannot contain -. By default, Header auto-converts: mapping _ in the parameter name to -, and since HTTP headers are case-insensitive, user_agent matches User-Agent.

python
from fastapi import FastAPI, Header

app = FastAPI()


@app.get("/header-strict/")
async def read_strict_header(
    # Some custom headers do use underscores (e.g. x_custom_key) — disable conversion then
    x_custom_key: str = Header(..., convert_underscores=False),
):
    return {"x_custom_key": x_custom_key}

Think twice before disabling conversion

Some HTTP proxies and servers reject headers containing underscores (Nginx drops them by default). Confirm your entire chain allows them before setting convert_underscores=False.

Duplicate headers with the same name (e.g., multiple X-Token headers) can be received as a list with Optional[List[str]]; a single str receives only one value.

8.3 Setting Cookies in Responses ​

Reading relies on parameter declarations; writing uses response.set_cookie(). Add a Response type parameter to the path function (you do not need to return it manually):

python
from fastapi import FastAPI, Response

app = FastAPI()


@app.post("/login/")
async def login(response: Response, username: str):
    response.set_cookie(
        key="session_id",
        value="s-123",
        max_age=3600,             # Validity in seconds
        httponly=True,            # Blocked from JS read — prevents XSS theft
        secure=True,              # Only sent over HTTPS
        samesite="lax",           # CSRF protection: cross-site requests do not carry it
        path="/",
    )
    return {"msg": f"{username} logged in"}

You can also call set_cookie on a JSONResponse returned directly. The three security flags are a common interview topic:

FlagPurpose
httponlyBrowser blocks JavaScript from reading via document.cookie, mitigating XSS session theft
secureOnly sent over HTTPS connections
samesiteThree levels: strict / lax / none; controls whether cross-site requests carry the cookie, mitigating CSRF

Delete a Cookie with response.delete_cookie(key) — internally it sets a Set-Cookie header with an expiry in the past.

8.4 Typical Scenario: Session Tracking and Client Info ​

Combining this chapter's knowledge for a common middleware need — tracking client origin and maintaining a simple session:

python
import uuid
from fastapi import FastAPI, Header, Response
from typing import Optional

app = FastAPI()


@app.get("/track/")
async def track_client(
    response: Response,
    x_forwarded_for: Optional[str] = Header(None),
    user_agent: Optional[str] = Header(None),
):
    session_id = str(uuid.uuid4())
    response.set_cookie(
        key="session_id",
        value=session_id,
        httponly=True,
        max_age=86400,
    )
    return {
        "session_id": session_id,
        "client_ip": x_forwarded_for or "unknown",
        "browser": user_agent or "unknown",
    }

8.5 Chapter Summary ​

  • Cookie() and Header() are sibling parameter classes to Query and Path;
  • convert_underscores=True (default) maps user_agent → User-Agent; set False for custom headers with underscores;
  • response.set_cookie() with httponly, secure, samesite for secure session management;
  • response.delete_cookie() expires the cookie by setting a past expiry date.

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. Why must you use Cookie() instead of just writing session_id: str = None?

2. What does the httponly flag prevent?

3. What is the effect of samesite="lax" on a cookie?

4. How does Header handle the name mismatch between Python variables (user_agent) and HTTP headers (User-Agent)?

🛠️ Hands-On Exercises ​

  1. Write a POST /login endpoint that accepts username and password as Form fields, sets a secure session Cookie, and returns a welcome message.
  2. Write a GET /profile endpoint that reads the session Cookie and the X-Client-Version header; return both values.
  3. Test with curl: send a request with -b "session_id=test123" -H "X-Client-Version: 2.0" and verify the response contains both values.

Proceed to Chapter 9: Error Handling and Custom Exceptions.