How sessions work
- User authenticates
- Server creates session, generates unique session ID
- Server sends ID to client (cookie)
- Client sends ID with every request
- Server looks up ID, identifies user
The session ID is the key. Anyone who has it can act as that user.
Token requirements
- Unpredictable. Cryptographically random. Not sequential, not derived from username.
- Long enough. 128+ bits (32 hex chars).
- Unique. No shared tokens.
Cookie configuration
Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=3600
| Flag | Why |
|---|---|
HttpOnly | JS can't read it |
Secure | HTTPS only |
SameSite=Lax | Mitigates CSRF |
Session lifecycle
Creation: New session only after successful auth. Prevents fixation.
Expiry:
| Type | Value |
|---|---|
| Idle timeout | 15-30 min |
| Absolute timeout | 8-24 hours |
Invalidation: Destroy server-side on logout. Don't just delete the cookie.
request.session.flush() # Django
req.session.destroy() # Express
Also invalidate on: password change, MFA change, admin revocation.
Regeneration: New ID after privilege change (login, admin elevation).
request.session.cycle_key() # Django
Session attacks
Fixation: Attacker sets victim's session ID before auth. Defence: regenerate ID on login.
Hijacking:
- XSS reads cookie → blocked by
HttpOnly - Network sniffing → blocked by
Secure+ HTTPS - Cross-site request → blocked by
SameSite
Replay: Short lifetimes limit window. Critical actions require re-auth.
Where to store state
Server-side (recommended): Database, Redis. Client holds only ID.
Signed cookies: Small data OK. 4KB limit. Data visible. Revocation needs key rotation.
JWTs: No revocation without server state. In localStorage = XSS accessible. If using: short expiry + server-side refresh tokens.
The takeaway
Crypto-random tokens. Configure cookies with HttpOnly, Secure, SameSite. Regenerate ID after login. Enforce timeouts. Destroy sessions on logout.
