🎓 What You Will Learn
- Cookie Basics: Understanding HTTP cookies
- Setting Cookies: Creating and storing cookies
- Reading Cookies: Accessing cookie values from requests
- Secure Cookies: HttpOnly, Secure, SameSite flags
- Sessions: Using Starlette SessionMiddleware
- Cookie Expiration: Managing cookie lifetime
1How Cookies Work
Cookies are small pieces of data that the browser stores. The server sends a cookie in a response. The browser saves it and sends it back with every future request. This makes cookies perfect for sessions and user preferences.
Cookie Lifecycle: Server sets cookie in response, browser stores it, browser includes it in all subsequent requests to that domain.
2Setting Cookies in FastAPI
Add a response: Response parameter to your endpoint. FastAPI fills it in for you. Call response.set_cookie(), then return your data as normal.
from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/set-cookie")
async def set_cookie(response: Response):
response.set_cookie(key="user_id", value="12345")
return {"message": "Cookie set"}
@app.post("/login")
async def login(username: str, response: Response):
response.set_cookie(
key="session_id",
value="abc123def456",
max_age=3600, # 1 hour
httponly=True, # Not accessible via JavaScript
secure=True, # HTTPS only
samesite="lax" # CSRF protection
)
return {"status": "logged in"}
How it works: FastAPI adds your cookie headers to the final JSON response. You do not need to build a Response object yourself.
3Reading Cookies from Requests
Access cookies from the request using the Cookie parameter.
from fastapi import FastAPI, Cookie
from typing import Optional
app = FastAPI()
@app.get("/get-cookie")
async def get_cookie(user_id: Optional[str] = Cookie(None)):
return {"user_id": user_id}
@app.get("/profile")
async def get_profile(session_id: str = Cookie(...)):
# session_id is required (... makes it mandatory)
return {"session_id": session_id, "user": "John"}
4Secure Cookie Flags
Always use security flags when setting sensitive cookies.
| Flag | Purpose | Use Case |
|---|---|---|
| HttpOnly | Cookie not accessible via JavaScript | Session tokens, auth cookies |
| Secure | Cookie only sent over HTTPS | Production environments |
| SameSite | Prevent CSRF attacks | Strict=same-site only, Lax=navigation allowed |
| Max-Age | Cookie expiration in seconds | Sessions, temporary tokens |
| Domain | Cookie accessible on specific domains | Sub-domain sharing |
from fastapi import FastAPI, Response
app = FastAPI()
@app.post("/login")
async def secure_login(username: str, response: Response):
response.set_cookie(
key="session_token",
value="signed_token_here",
max_age=86400, # 1 day
httponly=True, # Prevent JavaScript access
secure=True, # HTTPS only
samesite="strict", # Strict CSRF protection
domain="example.com" # Specific domain
)
return {"status": "logged in"}
Never use HttpOnly=False for sensitive cookies. Always protect session and auth cookies with HttpOnly flag.
5Using Starlette SessionMiddleware
For sessions, use Starlette's SessionMiddleware. It manages the session cookie for you. It needs the itsdangerous package.
pip install itsdangerousfrom fastapi import FastAPI, Request
from starlette.middleware.sessions import SessionMiddleware
app = FastAPI()
# Add session middleware
app.add_middleware(
SessionMiddleware,
secret_key="your-secret-key-here",
max_age=3600, # Session expires in 1 hour
https_only=True, # HTTPS only in production
same_site="lax"
)
@app.post("/login")
async def login(request: Request, username: str):
request.session["user"] = username
return {"message": f"Logged in as {username}"}
@app.get("/profile")
async def profile(request: Request):
user = request.session.get("user")
if not user:
return {"error": "Not logged in"}
return {"user": user}
@app.post("/logout")
async def logout(request: Request):
request.session.clear()
return {"message": "Logged out"}
SessionMiddleware: Stores session data in a signed cookie. No server-side storage needed.
Signed, not encrypted: The signature stops users from changing the data. But the data is only base64-encoded, so users can still read it. Never put passwords or secrets in the session.
6Managing Cookie Expiration
Cookies can expire automatically. Use max_age (seconds from now) or expires (an exact date).
from fastapi import FastAPI, Response
from datetime import datetime, timedelta, timezone
app = FastAPI()
@app.post("/set-temporary-cookie")
async def set_temp_cookie(response: Response):
# Expires in 30 minutes
response.set_cookie(
key="temp_token",
value="temp_value",
max_age=1800 # 30 minutes in seconds
)
return {"status": "temp cookie set"}
@app.post("/set-persistent-cookie")
async def set_persistent_cookie(response: Response):
# Expires at a specific datetime (must be timezone-aware UTC)
expires = datetime.now(timezone.utc) + timedelta(days=7)
response.set_cookie(
key="persistent",
value="persistent_value",
expires=expires
)
return {"status": "persistent cookie set"}
@app.post("/delete-cookie")
async def delete_cookie(response: Response):
# Tells the browser to remove the cookie right away
response.delete_cookie(key="session_id")
return {"status": "cookie deleted"}
Use UTC for expires: Pass a timezone-aware datetime like datetime.now(timezone.utc). A naive datetime.now() will crash when Starlette formats the cookie header.
7Testing Cookies
Test cookie handling with TestClient. It keeps cookies between requests, just like a real browser. The tests below use the endpoints we built in earlier sections.
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_set_cookie():
response = client.get("/set-cookie")
assert response.status_code == 200
assert "user_id" in client.cookies
def test_cookie_roundtrip():
# Set cookie
client.get("/set-cookie")
# Verify cookie is sent in next request
response = client.get("/get-cookie")
assert response.json()["user_id"] == "12345"
def test_session():
# Login (sets session)
client.post("/login?username=john")
# Verify session is available
response = client.get("/profile")
assert response.json()["user"] == "john"
# Logout
client.post("/logout")
# Verify session is cleared
response = client.get("/profile")
assert "error" in response.json()
8Cookie Security Best Practices
- Always use
httponly=Truefor session/auth cookies - Always use
secure=Truein production (HTTPS only) - Set appropriate
samesiteflag (Strict or Lax) - Use short
max_agefor sensitive cookies (1-24 hours) - Never store sensitive data directly in cookies (use sessions instead)
- Validate and sanitize cookie values on the server
- Use signed/encrypted cookies for integrity
- Clear cookies on logout
- Test cookie behavior across browsers
- Monitor for suspicious cookie access patterns
9Cookies with CORS
When using CORS with cookies, special configuration is needed.
from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware
from starlette.middleware.sessions import SessionMiddleware
app = FastAPI()
# Session middleware must come BEFORE CORS
app.add_middleware(SessionMiddleware, secret_key="secret")
# CORS must allow credentials
app.add_middleware(
CORSMiddleware,
allow_origins=["https://frontend.example.com"],
allow_credentials=True, # Important for cookies!
allow_methods=["*"],
allow_headers=["*"]
)
Middleware Order: SessionMiddleware must be added BEFORE CORSMiddleware. Middleware executes in reverse order (last added runs first).
10Common Cookie Patterns
| Pattern | Use Case | Example |
|---|---|---|
| Session cookie | Track logged-in user | session_id with HttpOnly |
| Preference cookie | Remember user preferences | theme, language, timezone |
| Tracking cookie | Analytics and behavior | Google Analytics _ga |
| CSRF token cookie | Prevent CSRF attacks | csrf_token matching form token |
| Auth cookie | Stateless authentication | JWT token in HttpOnly cookie |
11Debugging Cookies
Use browser DevTools to inspect cookies.
# Open browser DevTools (F12)
# Go to Application tab
# Click Cookies in left sidebar
# View all cookies for the domain
# Check cookie attributes:
# - Name: cookie name
# - Value: cookie value
# - Domain: which domain can access
# - Path: which paths can access
# - Expires/Max-Age: when it expires
# - HttpOnly: if JavaScript can access
# - Secure: if HTTPS only
# - SameSite: CSRF protection level
12Migrating from Cookies to Tokens
JWT tokens let you do stateless authentication. You can store the JWT inside an HttpOnly cookie. This keeps the token safe from JavaScript.
pip install pyjwtimport jwt
from fastapi import Cookie, FastAPI, HTTPException, Response
app = FastAPI()
SECRET_KEY = "change-me" # Load from an environment variable in production
def create_jwt_token(username: str) -> str:
return jwt.encode({"sub": username}, SECRET_KEY, algorithm="HS256")
@app.post("/login")
async def login(username: str, response: Response):
token = create_jwt_token(username)
# Store JWT in an HttpOnly cookie
response.set_cookie(
key="access_token",
value=token,
httponly=True,
secure=True,
max_age=3600
)
return {"message": "Logged in"}
@app.get("/profile")
async def profile(access_token: str = Cookie(...)):
try:
payload = jwt.decode(access_token, SECRET_KEY, algorithms=["HS256"])
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="Invalid token")
return {"user": payload["sub"]}
13Summary & Recommendations
You now know how cookies work in FastAPI. Use SessionMiddleware for simple sessions. Use JWT in an HttpOnly cookie for stateless APIs.
- FastAPI Cookie Parameters (official docs)
- Starlette SessionMiddleware (official docs)
- MDN: Using HTTP Cookies
🚀 Congratulations! You now understand how to securely handle cookies in FastAPI. Build session management and authentication with confidence!