[BEE-1004] Session Management
INFO
A session is server-side state bound to a client identifier. Generating sessions securely, protecting the session ID in transit, and destroying sessions reliably are the three non-negotiable requirements.
Context
HTTP is a stateless protocol. Once a user authenticates, the server needs a mechanism to recognize that same user across subsequent requests without requiring credentials on every call. The classical answer is a server-side session: the server creates a record keyed by a random identifier, stores it, and delivers the identifier to the client as a cookie. Every subsequent request presents that cookie; the server looks up the record and knows who is making the request.
Sessions appear simple but contain several failure modes that consistently appear in vulnerability reports:
- Predictable session IDs allow attackers to guess valid sessions.
- Session fixation lets an attacker pre-set a session ID before the victim logs in.
- Missing cookie security flags expose session IDs to JavaScript or plain-HTTP interception.
- Missing server-side invalidation means "logout" is cosmetic — the session stays usable.
OWASP's Session Management Cheat Sheet (2024) and NIST SP 800-63B (Section 7, Reauthentication) provide the normative foundation for the requirements in this article.
Principle
1. Session IDs MUST be generated with a cryptographically secure source
A session ID is only as unpredictable as its source of randomness. Predictable IDs — sequential counters, timestamps, user IDs — can be enumerated by an attacker.
Requirements:
- Generate session IDs using a CSPRNG (Cryptographically Secure Pseudorandom Number Generator), never
Math.random()or equivalent. - Session IDs MUST carry at least 128 bits of entropy (OWASP recommendation; NIST SP 800-63B requires a minimum of 64 bits for random values, 128 is the current practical standard).
- Session IDs MUST NOT encode user-identifiable data. The identifier must be opaque.
2. Cookie attributes MUST be set correctly
The session cookie is the primary attack surface for session ID theft. All four of the following attributes are required in production.
| Attribute | Required | Purpose |
|---|---|---|
HttpOnly | MUST | Prevents JavaScript (document.cookie) from reading the session ID. Mitigates XSS-based session theft. |
Secure | MUST | Restricts the cookie to HTTPS connections. Prevents plaintext transmission. (RFC 6265, Section 4.1.2.5) |
SameSite=Strict or Lax | SHOULD | Controls cross-site cookie submission. Strict blocks all cross-site sends; Lax allows top-level navigation. Mitigates CSRF. |
Path=/ | SHOULD | Constrains the cookie to the application path. Avoid overly broad Domain scoping. |
Example Set-Cookie header with all required attributes:
Set-Cookie: sessionId=a3f8c2d1e9b047f6a2d8c4e1f9b3a7d5;
HttpOnly;
Secure;
SameSite=Strict;
Path=/Do not set Expires or Max-Age on session cookies. A session cookie without these attributes expires when the browser session ends, reducing the window for a stolen cookie to be replayed.
3. Session IDs MUST be regenerated after authentication (session fixation prevention)
Session fixation is an attack where the attacker establishes a known session ID before the victim authenticates, then waits for the victim to log in and assumes their session. The defense is unconditional: discard the pre-authentication session ID and issue a new one immediately after a successful login.
The same regeneration applies after any privilege escalation (e.g., a user confirms their password to access a sensitive operation).
4. Sessions MUST be invalidated server-side on logout and timeout
Client-side logout that only deletes the cookie is insufficient. The server MUST delete or mark invalid the session record in its session store. A stolen cookie replayed after logout MUST return a rejection.
Two timeout policies SHOULD be enforced independently:
- Idle timeout: invalidate a session after a period of inactivity (e.g., 15–30 minutes for standard applications; 2–5 minutes for high-privilege operations). NIST SP 800-63B requires reauthentication after 30 minutes of inactivity at AAL2.
- Absolute timeout: invalidate a session after a fixed wall-clock duration regardless of activity (e.g., 8–24 hours for standard applications; 4–8 hours for sensitive contexts). NIST SP 800-63B requires reauthentication at least every 12 hours at AAL2.
Both timeouts MUST be enforced server-side. A client-side countdown is advisory UI only.
5. Session storage MUST support reliable lookup and deletion
Three common approaches at concept level:
- In-process memory: fast, zero infrastructure cost; loses all sessions on server restart; does not scale across multiple instances.
- Database-backed: sessions survive restarts and work across instances; requires indexed lookups on the session ID column; supports expiry via TTL or a cleanup job.
- Distributed cache: fast reads, built-in TTL expiry, horizontal scale; introduces an external dependency; requires high availability for the cache tier.
The choice depends on scalability requirements. Multi-instance deployments require either a shared store (database or cache) or sticky sessions with a load balancer. Sticky sessions without a shared store complicate server-side invalidation across the fleet.
6. Sessions vs. tokens: choose the right tool
Sessions and tokens (JWTs) are not interchangeable. The primary tradeoff is statefulness vs. scalability:
| Dimension | Server-side sessions | Stateless tokens (JWT) |
|---|---|---|
| Revocation | Immediate — delete the session record | Difficult — token is valid until expiry without a blocklist |
| Server state | Required (session store) | None (validation by signature) |
| Scalability | Requires shared session store at scale | Validates locally on each node |
| Payload size | Tiny cookie (ID only) | Token carries all claims |
| Auditability | Easy — server log contains session events | Requires log aggregation of token presentations |
Use server-side sessions when immediate revocation is a requirement (e.g., financial applications, admin consoles). Use stateless tokens when horizontal scalability with no shared state is the priority and the short expiry window (typically 15 minutes) is an acceptable revocation latency. See BEE-1002 for token design.
Visual
The following diagram shows the full session lifecycle from login through logout and timeout.
Example
The following pseudocode shows session creation on login and validation on each subsequent request. It is intentionally framework-agnostic.
# --- Login handler ---
function handle_login(request):
credentials = parse_credentials(request)
user = authenticate(credentials)
if user is null:
return response(401, "Invalid credentials")
# Discard any pre-login session (session fixation prevention)
old_session_id = read_cookie(request, "sessionId")
if old_session_id is not null:
session_store.delete(old_session_id)
# Create new session with fresh cryptographic ID
session_id = csprng.generate_hex(32) # 256 bits
session_data = {
user_id: user.id,
created_at: now(),
last_active: now(),
}
session_store.set(session_id, session_data, ttl=absolute_timeout)
response = response(200, "Logged in")
response.set_cookie(
name = "sessionId",
value = session_id,
http_only = true,
secure = true,
same_site = "Strict",
path = "/",
# No Expires / Max-Age — session cookie
)
return response
# --- Request validation middleware ---
function require_session(request):
session_id = read_cookie(request, "sessionId")
if session_id is null:
return response(401, "No session")
session = session_store.get(session_id)
if session is null:
return response(401, "Session not found or expired")
idle_seconds = now() - session.last_active
if idle_seconds > idle_timeout:
session_store.delete(session_id)
return response(401, "Session expired (idle timeout)")
# Refresh last_active
session.last_active = now()
session_store.set(session_id, session, ttl=absolute_timeout)
request.user_id = session.user_id
return next(request)
# --- Logout handler ---
function handle_logout(request):
session_id = read_cookie(request, "sessionId")
if session_id is not null:
session_store.delete(session_id) # Server-side invalidation
response = response(200, "Logged out")
response.set_cookie(
name = "sessionId",
value = "",
max_age = 0, # Instruct browser to delete cookie
)
return responseCommon Mistakes
1. Using predictable session IDs.
Sequential integers, user IDs, and timestamps are not session IDs. Any value an attacker can compute or enumerate is enumerable. Use CSPRNG output only.
2. Not regenerating the session ID after login.
If the server reuses the same session ID across the authentication boundary, an attacker who injects a known session ID before login can assume the victim's session after it. Regenerate unconditionally on every successful authentication.
3. Missing HttpOnly or Secure flags.
A session cookie without HttpOnly can be stolen by injected JavaScript. A cookie without Secure can be captured over HTTP on a mixed-content page or a network the attacker controls. Both flags are required in production; neither has a legitimate reason to be absent.
4. No session timeout.
A session without an idle or absolute timeout is valid forever, or until the user explicitly logs out. If a user never logs out (closed tab, stolen device), the session remains exploitable indefinitely. Implement both idle and absolute timeouts enforced server-side.
5. Not invalidating the session server-side on logout.
Clearing the session cookie without deleting the server-side record leaves the session usable. Any attacker who captured the session ID before logout can continue to use it. Logout MUST delete the session record in the session store first, then clear the cookie.
Related BEPs
- BEE-1001: Authentication vs Authorization — identity pipeline context
- BEE-1002: Token-Based Authentication — stateless alternative to sessions
- BEE-2001: Transport Security — HTTPS requirement underlying Secure cookie
- BEE-2004: Secrets Management — handling secrets in session stores
References
- OWASP, "Session Management Cheat Sheet" (2024). https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html
- Barth, A., "HTTP State Management Mechanism" RFC 6265 (April 2011). https://datatracker.ietf.org/doc/html/rfc6265
- NIST, "Digital Identity Guidelines: Authentication and Lifecycle Management" SP 800-63B (2017, updated 2020). https://pages.nist.gov/800-63-3/sp800-63b.html
- OWASP, "Top 10 — A07:2021 Identification and Authentication Failures" (2021). https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures/