3 min read
On this page

Session Management

What is a Session?

HTTP is stateless. Every request is independent. Sessions provide the illusion of state by associating a series of requests with a specific user. After authentication, the server issues a session identifier that the client sends with each subsequent request.

Session management is a critical security control. If an attacker obtains a valid session token, they have the user's access without needing their password.

Session Tokens

A session token must be unpredictable, unique, and protected in transit and storage.

Generating Secure Tokens

Requirements:
- At least 128 bits of entropy (OWASP recommendation)
- Generated using a cryptographically secure random number generator
- Not derived from user data (username, email, timestamp)
- Not sequential or predictable

Bad token generation:
  token = base64(username + ":" + timestamp)
  token = md5(user_id + secret_key)
  token = str(auto_increment_id)

Good token generation:
  Python: secrets.token_hex(32)  # 256 bits of entropy
  Node:   crypto.randomBytes(32).toString('hex')
  Go:     crypto/rand.Read(make([]byte, 32))

The language's standard cryptographic library handles this.
Do not build your own token generation.

Session tokens stored in cookies must have proper security flags.

Set-Cookie: session=abc123def456;
  HttpOnly;
  Secure;
  SameSite=Lax;
  Path=/;
  Max-Age=3600;
  Domain=app.example.com

HttpOnly:
  Cookie is not accessible via JavaScript (document.cookie).
  Prevents XSS from stealing session tokens.
  This is non-negotiable for session cookies.

Secure:
  Cookie is only sent over HTTPS connections.
  Prevents interception on unencrypted connections.
  Always set this in production.

SameSite:
  Controls when cookies are sent with cross-site requests.
  - Strict: Never sent cross-site (breaks legitimate links from email)
  - Lax:    Sent on top-level navigations (GET), not on POST/fetch
  - None:   Always sent (requires Secure flag, needed for cross-site APIs)
  Lax is the recommended default for session cookies.

Path:
  Limits which paths the cookie is sent to.
  Use "/" for the session cookie unless there's a specific reason not to.

Domain:
  Limits which domains the cookie is sent to.
  Set this explicitly. Do not use wildcard domains.
Real example: 2012 Firesheep attack
- Tool that captured session cookies on unencrypted WiFi
- Worked on Facebook, Twitter, Amazon, and many others
- Demonstrated why the Secure flag and HTTPS are essential
- Led to widespread adoption of HTTPS across major sites

Session Lifecycle

Create on Login

When a user successfully authenticates:
1. Verify credentials (password hash comparison)
2. Invalidate any pre-existing session (prevents fixation)
3. Generate a new session token
4. Store session data server-side (user ID, creation time, IP)
5. Set the session cookie with proper flags
6. Log the authentication event

Important: Generate the session AFTER authentication, not before.
A session created before login should be replaced with a new one.

Invalidate on Logout

When a user logs out:
1. Delete the session from the server-side session store
2. Clear the session cookie (set Max-Age=0 or Expires in the past)
3. Log the logout event

Common mistake: Only deleting the cookie without invalidating
the server-side session. The old token remains valid — an attacker
who captured it can still use it.

Expire After Inactivity

Two types of session expiration:

Absolute timeout:
  Session expires after a fixed time regardless of activity.
  Banking: 15-30 minutes
  SaaS: 8-24 hours
  Social media: days to weeks

Idle timeout:
  Session expires after a period of inactivity.
  Reset the timer on each request.
  Banking: 5-15 minutes of inactivity
  SaaS: 30-60 minutes of inactivity

Implementation:
  Store last_activity_time in the session.
  On each request: check if (now - last_activity_time) > idle_timeout.
  If expired: destroy session, redirect to login.
  If valid: update last_activity_time.

Both types should be enforced. Absolute timeout prevents
stolen sessions from being used indefinitely by keeping them active.

Session Fixation Prevention

Session fixation is an attack where the attacker sets the session ID before the user authenticates, then uses that known session ID after the user logs in.

Attack flow:
1. Attacker creates a session on the target site (gets session ID "abc123")
2. Attacker tricks victim into using that session ID:
   - Link with session in URL: https://app.com/login?sid=abc123
   - Setting a cookie via XSS or a subdomain
3. Victim logs in using session "abc123"
4. The session is now authenticated and associated with the victim's account
5. Attacker uses session "abc123" — they are now the victim

Prevention: Regenerate the session ID on login.
The old session ID "abc123" becomes invalid.
A new session ID is issued that the attacker does not know.
Regeneration points (always regenerate the session ID when):
- User logs in
- User's privilege level changes (regular user becomes admin)
- User performs a sensitive operation
- A significant time has passed

Most web frameworks handle this with a single call:
  PHP:    session_regenerate_id(true)
  Python: request.session.cycle_key()
  Ruby:   reset_session
  Java:   request.getSession().invalidate(); request.getSession(true);
  Node (Express): req.session.regenerate()

Concurrent Session Control

Should a user be allowed to have multiple active sessions (multiple devices, multiple browsers)?

Options:

Allow unlimited sessions (most common):
  - User logs in from phone, laptop, tablet — all work
  - Simplest implementation
  - Risk: stolen session goes unnoticed because the user's
    own session still works

Limit to N sessions:
  - Allow 3-5 concurrent sessions
  - When limit is exceeded, invalidate the oldest session
  - Provides some protection without inconveniencing users

Single session (strictest):
  - New login invalidates all previous sessions
  - User immediately notices unauthorized access (they get logged out)
  - Can be frustrating for legitimate multi-device use
  - Good for banking and high-security applications

Recommended: Allow concurrent sessions but show users their active
sessions and let them revoke any they do not recognize.
Google, GitHub, and most major platforms take this approach.
Implementation:
  Store sessions in a database or Redis (not just memory):
  {
    session_id: "a8f5f167...",
    user_id: 42,
    created_at: "2025-03-15T10:30:00Z",
    last_active: "2025-03-15T14:22:00Z",
    ip_address: "203.0.113.50",
    user_agent: "Mozilla/5.0...",
    device_name: "Chrome on macOS"
  }

  Expose an endpoint: GET /account/sessions
  Allow revocation: DELETE /account/sessions/{session_id}

Server-Side vs Client-Side Sessions

Server-Side Sessions

The server stores session data (user ID, permissions, preferences) and only sends an opaque identifier to the client.

Flow:
  Client sends: Cookie: session=a8f5f167f44d...
  Server looks up: sessions["a8f5f167f44d..."] -> { user_id: 42, role: "admin" }
  Server knows who the user is and what they can do

Storage backends:
  - In-memory (fast, lost on restart, not horizontally scalable)
  - Redis (fast, persistent, shared across servers)
  - Database (slower, persistent, auditable)
  - File system (simple, not recommended for production)

Advantages:
  - Server controls all session data
  - Session can be revoked instantly (delete from store)
  - No sensitive data sent to the client
  - Session data size is unlimited

Disadvantages:
  - Requires server-side storage
  - Horizontal scaling requires shared session store (Redis)
  - Additional infrastructure dependency

Client-Side Sessions (JWT)

JSON Web Tokens store session data in the token itself, signed by the server. The client sends the entire token with each request.

JWT structure:
  header.payload.signature

  Header:  { "alg": "HS256", "typ": "JWT" }
  Payload: { "sub": "42", "role": "admin", "exp": 1710000000 }
  Signature: HMAC-SHA256(header + "." + payload, secret_key)

The server verifies the signature to trust the claims in the payload.
JWT advantages:
  - No server-side session store needed
  - Works well for stateless microservices
  - Can be verified by any service with the secret key (or public key for RSA)

JWT disadvantages:
  - Cannot be revoked without additional infrastructure
    (once issued, valid until expiry)
  - Size grows with claims (sent on every request)
  - Token theft grants access until expiry
  - Common implementation mistakes lead to vulnerabilities

JWT Security Pitfalls

Real vulnerability: "alg": "none" attack
  - JWT spec allows "none" as an algorithm (no signature)
  - Attacker changes the header to { "alg": "none" }
  - Removes the signature
  - Some libraries accept this token as valid
  - Fix: Always validate the algorithm server-side,
    reject "none" and unexpected algorithms

Real vulnerability: HMAC/RSA confusion
  - Server uses RSA (asymmetric): signs with private key,
    verifies with public key
  - Attacker changes alg to HS256 (symmetric)
  - Signs with the public key (which is public)
  - Vulnerable libraries use the public key as the HMAC secret
  - Fix: Explicitly specify the expected algorithm when verifying

Token expiration:
  - Short-lived access tokens (5-15 minutes)
  - Longer-lived refresh tokens (hours to days)
  - Refresh tokens should be stored securely (HttpOnly cookie, not localStorage)
  - Refresh tokens can be revoked server-side (requires a store)

When to Use Which

Use server-side sessions when:
  - You need instant session revocation
  - You have a monolithic application
  - You need to store large amounts of session data
  - Security is the primary concern (banking, healthcare)

Use JWTs when:
  - You have stateless microservices
  - You need cross-service authentication
  - You accept the trade-off of non-revocable tokens
  - You use short expiry times with refresh token rotation

Hybrid approach (common in practice):
  - JWT for API authentication between services (short-lived)
  - Server-side sessions for user-facing web application
  - Refresh tokens stored server-side for revocation capability

Session Hijacking Prevention

Attack vectors for session theft:

XSS (Cross-Site Scripting):
  - Attacker injects JavaScript that reads document.cookie
  - Prevention: HttpOnly flag prevents JavaScript access to cookies
  - Also: Content Security Policy, input/output encoding

Network sniffing:
  - Attacker captures session cookie over unencrypted connection
  - Prevention: Secure flag, HTTPS everywhere, HSTS

Session fixation:
  - Attacker sets the session ID before authentication
  - Prevention: Regenerate session ID on login

Malware / browser extensions:
  - Malicious software on the user's device reads cookies
  - Prevention: Limited — this is a client-side compromise
  - Mitigate with device verification and session monitoring

Cross-Site Request Forgery (CSRF):
  - Attacker makes the browser send authenticated requests
  - Not session theft, but session abuse
  - Prevention: SameSite cookies, CSRF tokens, checking Origin header

Common Pitfalls

  • Storing session tokens in localStorage: localStorage is accessible via JavaScript and vulnerable to XSS. Session tokens belong in HttpOnly cookies. localStorage is appropriate for non-sensitive UI preferences.

  • Not regenerating session ID on login: This enables session fixation attacks. Always generate a new session ID when a user's authentication state changes.

  • Using JWTs as a session replacement without understanding the trade-offs: JWTs cannot be revoked without building revocation infrastructure (which negates the "stateless" advantage). If you need instant logout, use server-side sessions.

  • Setting long expiration on JWTs: A JWT with a 24-hour expiry that gets stolen gives the attacker 24 hours of access. Use short-lived access tokens (5-15 minutes) with refresh token rotation.

  • Not expiring sessions server-side: Relying only on cookie expiry means the server never cleans up. Old sessions accumulate and remain valid. Enforce expiry on the server side.

  • Transmitting session tokens in URLs: URL parameters appear in browser history, referrer headers, server logs, and proxy logs. Never put session tokens in URLs.

Key Takeaways

  • Session tokens must be cryptographically random (128+ bits of entropy), stored in cookies with HttpOnly, Secure, and SameSite flags.
  • Regenerate the session ID on login to prevent session fixation. Invalidate sessions on logout by destroying the server-side record.
  • Enforce both absolute timeout and idle timeout. Banking applications need short timeouts; SaaS applications can be longer.
  • Server-side sessions provide instant revocation and are more secure for user-facing applications. JWTs work well for stateless service-to-service authentication.
  • JWT pitfalls are well-documented: "alg: none", HMAC/RSA confusion, long expiry, and storing in localStorage. Understand them before choosing JWTs.
  • Show users their active sessions and let them revoke unrecognized ones. This is both a security control and a transparency measure.