Sessions & Cookies: Real State Across Stateless Requests
Building a Web Framework
Chapter 6 ยท Sessions & Cookies: Real State Across Stateless Requests
Every request this course has built so far starts from nothing — a fresh environ dict, a fresh Request, no memory of anything a previous request did. That's genuinely how HTTP works: each request really is independent. A shopping cart, a "logged in" state, a visit counter all need something to survive across that gap anyway — and this chapter builds it exactly the way Chapter 5's own closing paragraph predicted: as ordinary middleware, reading a cookie before next_() and writing one after it returns.
Cookies: The Real Header on Both Sides
A cookie is nothing more than a header. The server sends Set-Cookie in a response; the browser sends it straight back as Cookie on every later request to the same site. Python's own standard library already parses and builds the real, correct syntax — http.cookies.SimpleCookie:
Wired into this chapter's own real Request and Response classes:
A Real Session Store
Cookies alone can only carry text the client sends back verbatim — anything sensitive still needs to live on the server, keyed by an ID the cookie merely points at. A minimal in-memory store, with a genuine, per-session idle-timeout:
Wiring It in as Ordinary Middleware
No special-case machinery needed — the exact before-next_()/after-next_() shape Chapter 5 already built, reading the session cookie first and writing an updated one last:
The Real Payoff: Why Sessions Need Cookies
Run the exact same code two genuinely different ways against a real live server:
urlopen() calls, with no jar, each receive their own real, distinct Set-Cookie header with a completely different session ID — and because nothing ever sends either cookie back, request.cookies.get('session_id') finds nothing on the second call, so make_session_middleware genuinely creates a brand-new session every single time. The count stays at 1 forever, not because of a bug, but because this client is never actually reusing anything. Switching to a real http.cookiejar.CookieJar — which automatically stores every real Set-Cookie it receives and resends it on every later request to the same host, exactly what a real browser does — makes the identical server-side code correctly count 1, 2, 3. The session was never really "in" the server alone; it only works because the cookie genuinely round-trips.
Server-Side Expiry Is a Genuinely Separate Control From the Cookie's Own Max-Age
A cookie's own Max-Age only governs when a well-behaved client stops sending it — nothing stops a client from ignoring that and resending an old cookie anyway. Real security has to come from the server checking independently, which is exactly what SessionStore.load()'s own timeout already does. Isolating that from the cookie's client-side lifetime means deliberately setting the two to genuinely different real values:
1; real request 2, immediately after, counts 2. After sleeping 1.5 real seconds — past the server's own 1-second timeout, nowhere near the cookie's own 3600-second Max-Age — the jar was checked directly and confirmed still holding, and about to resend, the exact same session ID from request 1. The real third request nonetheless counts 1 again: a brand-new session, created because SessionStore.load() found the old one past its own real timeout and deleted it — entirely independent of whether the client ever intended to stop using that cookie.
Session ID Security: Unpredictability, and HttpOnly
A session ID that could be guessed would let an attacker impersonate another real user with zero access to their machine or cookies at all — which is exactly why secrets.token_urlsafe(), not random or a counter, generates them:
secrets.token_urlsafe(32) draws from Python's own real, documented cryptographically-secure random source, producing a 43-character real string with 256 bits of genuine entropy — 20,000 real calls in a row produced 20,000 real, distinct values.
set_cookie()'s own default of http_only=True ties directly back to Chapter 4's own real, verified XSS vulnerability — unescaped output there let a genuine <script> tag execute in the page. Without HttpOnly, that same script could read document.cookie and steal a real session ID outright:
HttpOnly refuses to expose a cookie carrying it to any JavaScript running on the page at all — including a script Chapter 4's own real XSS vulnerability might have injected. Without the flag, that identical injected script has a real, working path straight to a live session ID. Defaulting http_only=True means a developer has to deliberately opt out to create that exposure, not deliberately opt in to close it.
Where This Course Is Headed
Chapter 7 builds a minimal ORM — mapping real Python objects to real SQL — giving SessionStore an honest, real alternative to its own current in-memory dict, one that survives an actual server restart.
Hands-On Exercises
Add a real Response.delete_cookie(name) method (a Set-Cookie with Max-Age=0) and a /logout route that calls it, register both on a real App with the session middleware, and verify with a real cookie jar that a request made right after logout starts a brand-new session (the visit counter resets to 1) rather than reusing the old one.
๐ View solutionBuild two real Response objects, one with set_cookie(..., http_only=True) and one with http_only=False, and verify directly on each one's own real Set-Cookie header string that HttpOnly is present in exactly one of the two โ then explain in your own words, tying back to Chapter 4's own real XSS finding, what a real attacker's injected script could and couldn't do differently between the two cases.
๐ View solutionUsing this chapter's own visit_counter_handler and session middleware, create two separate, independent http.cookiejar.CookieJar-backed openers (simulating two different real browsers) against the same running App, interleave real requests from both, and verify each one's own visit count increments completely independently of the other.
๐ View solutionChapter 6 Quick Reference
- Cookies โ a plain header pair, Set-Cookie/Cookie, real-parsed and real-built via http.cookies.SimpleCookie
- Request.cookies / Response.set_cookie() โ lazy-parsed cookie access, and a header-building helper defaulting to HttpOnly
- SessionStore โ a real in-memory dict of sid -> (data, created_at), with a genuine per-session idle-timeout on load()
- Session middleware โ the exact before-next_()/after-next_() shape from Chapter 5: load or create before, save and set the cookie after
- Why sessions need cookies โ verified live: no cookie jar means a brand-new session every request (stuck at 1); a real jar makes the identical code count 1, 2, 3
- Server-side expiry is independent โ verified live: the client still holds and sends the same unexpired-by-Max-Age cookie; only the server's own timeout invalidates it
- Session ID security โ secrets.token_urlsafe(32), verified with zero collisions across 20,000 real IDs; HttpOnly defaults on, closing the exact cookie-theft path Chapter 4's own real XSS finding opened
- Next chapter: A minimal ORM โ mapping real Python objects to real SQL