FastAPI

FastAPI Cookies — Session Management & Client Storage

Thirdy Gayares
12 min read

🎓 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
CookiesSessionsSecurity

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.

app/main.py
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.

app/main.py
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.

FlagPurposeUse Case
HttpOnlyCookie not accessible via JavaScriptSession tokens, auth cookies
SecureCookie only sent over HTTPSProduction environments
SameSitePrevent CSRF attacksStrict=same-site only, Lax=navigation allowed
Max-AgeCookie expiration in secondsSessions, temporary tokens
DomainCookie accessible on specific domainsSub-domain sharing
app/main.py
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 itsdangerous
app/main.py
from 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.

Cookies can expire automatically. Use max_age (seconds from now) or expires (an exact date).

app/main.py
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.

tests/test_cookies.py
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=True for session/auth cookies
  • Always use secure=True in production (HTTPS only)
  • Set appropriate samesite flag (Strict or Lax)
  • Use short max_age for 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.

app/main.py
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

PatternUse CaseExample
Session cookieTrack logged-in usersession_id with HttpOnly
Preference cookieRemember user preferencestheme, language, timezone
Tracking cookieAnalytics and behaviorGoogle Analytics _ga
CSRF token cookiePrevent CSRF attackscsrf_token matching form token
Auth cookieStateless authenticationJWT 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 pyjwt
app/main.py
import 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.

🚀 Congratulations! You now understand how to securely handle cookies in FastAPI. Build session management and authentication with confidence!

About the Author

TG

Thirdy Gayares

Passionate developer creating custom solutions for everyone. I specialize in building user-friendly tools that solve real-world problems while maintaining the highest standards of security and privacy.