Once a user logs in, the session token is their identity for the rest of the visit. Whoever holds that token is treated as that user, no questions asked. So everything in this lesson comes down to one idea: make the token hard to steal, hard to guess, and quick to invalidate.
Here's the loop it sits inside:
- The user authenticates.
- The server creates a session and generates a unique session ID.
- The server sends that ID to the client as a cookie.
- The client returns the ID on every request.
- The server looks it up and knows who's calling.
What the token has to be
- Unpredictable. Cryptographically random. Never sequential, never derived from the username.
- Long enough. At least 128 bits, which is 32 hex characters.
- Unique. No sharing tokens between users or devices.
Configuring the cookie
Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=3600
| Flag | Why it's there |
|---|---|
HttpOnly | JavaScript can't read it, so XSS can't steal it |
Secure | Only sent over HTTPS |
SameSite=Lax | Mitigates CSRF |
Managing the lifecycle
This is where most session bugs actually live, not in the token generation.
Creation. Issue a new session only after a successful login. Reusing a pre-login session is what enables fixation.
Expiry. Two clocks, not one:
| Type | Value |
|---|---|
| Idle timeout | 15 to 30 minutes |
| Absolute timeout | 8 to 24 hours |
Invalidation. On logout, destroy the session on the server. Deleting the cookie alone leaves a valid token that still works if someone captured it.
request.session.flush() # Django
req.session.destroy() # Express
Also invalidate on password change, MFA change, and admin revocation.
Regeneration. Issue a fresh ID whenever privilege changes, such as at login or when someone elevates to admin.
request.session.cycle_key() # Django
How sessions get attacked
Fixation. The attacker plants a known session ID on the victim before they log in, then rides the now-authenticated session. Regenerating the ID at login closes it.
Hijacking. Three routes in, each blocked by one of your flags: XSS reading the cookie (blocked by HttpOnly), network sniffing (blocked by Secure plus HTTPS), and cross-site requests (blocked by SameSite).
Replay. A captured token reused later. Short lifetimes shrink the window; re-authentication on critical actions closes it.
Where to keep the state
Server-side is the safe default. Hold the session in a database or Redis and give the client only the ID.
Signed cookies can hold a little data, capped at 4KB, but the data is visible to the user and revoking it means rotating keys.
JWTs can't be revoked without server-side state, and parked in localStorage they're readable by any XSS. If you use them, keep them short-lived and back them with server-side refresh tokens.
When in doubt, keep the state on the server, regenerate the ID at every privilege change, and make sure logout actually means logged out.
