Sessions & Cookies
Authentication (Chapters 2–3) happens once, at login. But HTTP is stateless — every request is independent, with no memory of the last. So how does the server know that this request comes from someone who logged in five minutes ago? The answer is the session: the mechanism that carries "you are logged in" across many stateless requests. This is the thing all three previous courses kept calling "the session."
The Problem: HTTP Has No Memory
Each HTTP request stands alone. Without a session, a user would have to send their password on every single request — terrible for security (the credential constantly in flight) and UX. The session solves this: log in once, then prove "I'm still that logged-in user" cheaply on each subsequent request, without resending the password.
The Session ID: A Bearer Token for "Logged In"
After a successful login, the server creates a session and issues a session ID — a long, random, unguessable string. The client sends that ID back on every request, and the server uses it to recognize the user. The login lifecycle:
Set-Cookie: sessionId=…).document.cookie, or sniffing it over plain HTTP) equals account takeover without the password (XSS course Ch. 5), and why CSRF can ride it (CSRF course). The entire rest of this course's session material is about protecting this one secret string — keeping it confidential, unguessable, and short-lived. Treat the session ID with the same care as a password.
Cookies: The Usual Session Carrier
The session ID needs somewhere to live in the browser and ride along on requests. The standard home is a cookie, because cookies are sent automatically by the browser to their origin — the user (and the app code) doesn't have to attach the ID manually:
That "sent automatically" property is convenient — and is precisely the double-edged sword behind CSRF (the cookie rides even on forged cross-site requests). The cookie's security attributes — HttpOnly, Secure, SameSite — are what make it safe to carry a session ID; they get their own deep treatment in the next chapter. (Session IDs can also be carried in other ways — e.g. an Authorization header for APIs — covered with tokens in Chapter 7.)
Where the Session Data Lives: Server-Side vs Client-Side
There are two fundamentally different models for where the actual session state is stored. This is one of the most important architectural choices in auth:
| Server-side (stateful) | Client-side (stateless) | |
|---|---|---|
| Cookie holds | just an opaque session ID | the session data itself (signed/encrypted) |
| Data stored in | server memory / Redis / database | the cookie (or a token) on the client |
| Server lookup | yes — find session by ID each request | no — verify the signature & read it |
| Revocation | easy — delete the server record | hard — can't un-issue a self-contained token |
| Scaling | needs shared store across servers | no shared store needed |
Server-Side Sessions (the classic, default model)
The cookie holds only a meaningless random ID; all the real data (user ID, roles, login time) lives on the server, keyed by that ID. This is the traditional approach (PHP sessions, Express express-session, Rails) and the safest default. Its standout advantage: instant revocation — to log someone out everywhere (or kill a stolen session), you just delete the server-side record and the ID becomes worthless.
Client-Side Sessions (stateless)
The cookie (or token) contains the session data itself, protected by a cryptographic signature so the client can't tamper with it. The server stores nothing — it just verifies the signature and reads the data. This scales beautifully (no shared session store) and is the model behind JWTs (Chapter 7).
What Makes a Good Session ID
- High entropy — long and generated by a cryptographically secure RNG (≥128 bits), so it can't be guessed or brute-forced.
- Opaque — meaningless and random; it should encode nothing (not the user ID, not a counter). A predictable or sequential ID is a catastrophe (Exercise 3).
- Unique — never reused across sessions or users.
- Generated by the framework — use your framework's session machinery (it uses a CSPRNG); never hand-roll IDs from timestamps, counters, or
Math.random().
Hands-On Exercises
Explain why HTTP being stateless makes sessions necessary, and trace the five-step session lifecycle from login to a later authenticated request. Identify precisely why the server doesn't need the password again after step 1, and what the session ID represents.
📄 View solutionCompare server-side and client-side sessions across storage, per-request cost, scaling, and revocation. For each of these requirements, recommend a model and justify: (a) "log out this stolen session immediately"; (b) "scale to 50 stateless API servers with no shared store."
📄 View solutionA site issues sequential session IDs (user-1001, user-1002, …) or IDs derived from Math.random()/timestamps. Explain the attack each enables and how an attacker hijacks other users' sessions. State the properties a session ID must have and why the framework's generator should be used.
Chapter 4 Quick Reference
- HTTP is stateless → the session carries "you are logged in" across requests without resending the password
- Session ID = a long random unguessable string issued at login; the client returns it each request
- The session ID is a bearer token — whoever holds it is "you"; protect it like a password (XSS theft / CSRF riding target it)
- Cookies are the usual carrier (auto-sent to origin); their
HttpOnly/Secure/SameSiteattributes secure it (next chapter) - Server-side (stateful) — cookie holds only an opaque ID; data on server; easy revocation, needs shared store
- Client-side (stateless) — cookie/token holds signed data; scales without a store; hard to revoke (JWTs, Ch. 7)
- Core trade-off: scaling (stateless) vs revocation (stateful); server-side is the simpler, safer default for most apps
- Good session ID: high-entropy (≥128-bit CSPRNG), opaque, unique, framework-generated — never sequential/predictable
- Next chapter: session security — fixation, hijacking, rotation, timeouts, and the cookie flags in full