Sessions & Cookies

Chapter 4
Sessions & Cookies
How the session works after login — session IDs, cookies, server-side vs client-side

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:

User submits correct credentials (Chapters 2–3 verify them).
Server creates a session and generates a random session ID (high entropy, unguessable).
Server sends the ID to the client, normally in a cookie (Set-Cookie: sessionId=…).
Browser automatically returns the cookie on every subsequent request to that site.
Server looks up the session by ID → knows who the user is, no password needed.
The session ID IS the identity — anyone holding it is "you"
A session ID is a bearer token: whoever presents it is treated as the authenticated user, no questions asked. The server doesn't re-check your password — it trusts the ID. That's exactly why stealing the session ID (XSS reading 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:

# server sets the session cookie on login Set-Cookie: sessionId=9f8c1a...e2; HttpOnly; Secure; SameSite=Lax; Path=/ # browser auto-sends it on every later request to the site Cookie: sessionId=9f8c1a...e2

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 holdsjust an opaque session IDthe session data itself (signed/encrypted)
Data stored inserver memory / Redis / databasethe cookie (or a token) on the client
Server lookupyes — find session by ID each requestno — verify the signature & read it
Revocationeasy — delete the server recordhard — can't un-issue a self-contained token
Scalingneeds shared store across serversno 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).

The core trade-off: easy scaling vs easy revocation
Stateless (client-side) sessions are wonderful for scaling — any server can validate the token with just the signing key, no database hit, no shared session store. But they have a hard problem: revocation. Because the token is self-contained and valid until it expires, you can't simply "delete" it — if it's stolen, it keeps working until expiry. Server-side sessions are the mirror image: a quick lookup per request and a shared store to manage, but you can revoke instantly by deleting the record. There's no free lunch — Chapter 7 covers the patterns (short-lived tokens + refresh, denylists) used to claw back revocation for stateless sessions. For most apps, server-side sessions remain the simpler, safer default.

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

Exercise 1

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 solution
Exercise 2

Compare 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 solution
Exercise 3

A 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.

📄 View solution

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/SameSite attributes 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