What is Session Management?
Session management maintains the authenticated state of a user across multiple HTTP requests. HTTP is stateless — without sessions, users would need to re-authenticate on every request. Two primary approaches: server-side sessions (store session data on the server, give the client a session ID) and client-side sessions (store session data in a signed token on the client, e.g., JWT).
Server-Side Sessions (Redis)
# On login:
session_id = generate_secure_random_token(32) # 256-bit
session_data = {'user_id': 123, 'role': 'admin',
'created_at': now(), 'last_active': now()}
redis.setex(f'session:{session_id}', 3600, json.dumps(session_data))
# Set cookie: Set-Cookie: session_id={session_id}; HttpOnly; Secure; SameSite=Strict
# On request:
session_id = request.cookies['session_id']
data = redis.get(f'session:{session_id}')
if not data: redirect_to_login()
session = json.loads(data)
redis.expire(f'session:{session_id}', 3600) # extend TTL on activity
Advantages: instant revocation (delete the Redis key), small cookie payload, session data stays on server. Disadvantages: Redis is in the critical path of every authenticated request; Redis becomes a single point of failure (mitigate with Redis Cluster + read replicas).
JWT (JSON Web Tokens) — Stateless Sessions
# Structure: header.payload.signature (Base64URL encoded)
# Payload:
{
"user_id": 123,
"role": "admin",
"iat": 1620000000, # issued at
"exp": 1620003600, # expires at (1 hour)
"jti": "unique-token-id" # JWT ID for revocation
}
# Signed with HS256 (HMAC-SHA256) using a server-side secret
Advantages: stateless — no server-side storage, scales horizontally. Verifying a JWT requires only the secret key, no DB lookup. Disadvantages: revocation is hard — a JWT is valid until expiry even if the user logs out. Mitigate with a short expiry (15 minutes) + refresh token. Payload is visible (Base64 encoded, not encrypted) — don’t put sensitive data in JWT.
Refresh Token Pattern
Short-lived access token (JWT, 15 min) + long-lived refresh token (opaque token, 30 days). The access token is used for API calls. When it expires, the client sends the refresh token to get a new access token without re-authentication. Refresh tokens are stored server-side (Redis or DB) and can be revoked. On logout: invalidate the refresh token. Rotation: each use of the refresh token issues a new one and invalidates the old — limits the window of refresh token theft.
Concurrent Session Management
Limit concurrent sessions per user (e.g., max 3 devices). Track active sessions: SADD user_sessions:{user_id} {session_id}; store session metadata (device, IP, last_active). On new login: if session count >= max, revoke the oldest session. On logout: SREM user_sessions:{user_id} {session_id}; delete the session key. Admin can revoke all sessions: SMEMBERS user_sessions:{user_id} → delete each session key → delete the set.
Security Hardening
- HttpOnly cookie: prevents JavaScript from reading the session cookie (XSS protection)
- Secure flag: cookie only sent over HTTPS
- SameSite=Strict: prevents CSRF — cookie not sent on cross-site requests
- Session regeneration: after login, generate a new session ID (prevents session fixation attacks)
- IP/User-agent binding (optional): invalidate session if IP or user-agent changes — reduces session hijacking risk, but breaks for mobile users on mobile networks
Key Design Decisions
- Redis sessions for monolith/small services — instant revocation, simple implementation
- JWT for microservices — stateless, no inter-service session lookup
- Short JWT + refresh token — combines stateless benefits with revocation capability
- Always HttpOnly + Secure + SameSite cookies — baseline security
- Session regeneration on privilege change (login, sudo) — prevents session fixation
{“@context”:”https://schema.org”,”@type”:”FAQPage”,”mainEntity”:[{“@type”:”Question”,”name”:”What is the difference between server-side sessions and JWT tokens?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Server-side sessions: on login, generate a random session ID (e.g., 32-byte hex string), store session data (user_id, roles, permissions) in Redis with TTL. Send the session ID as a cookie. On each request, look up Redis to get the session data. Advantages: instant revocation (delete the Redis key), session data can be large (stays on server), sensitive data never leaves the server. Disadvantages: Redis is in the hot path of every request; Redis is a dependency that must be highly available. JWT (JSON Web Token): session data is encoded in the token itself and signed (not encrypted). No server-side storage needed. On each request, verify the signature using the secret key — no DB or Redis lookup. Advantages: stateless, horizontally scales without a shared session store. Disadvantages: revocation is hard (token is valid until expiry); keep expiry short (15-60 minutes) and use refresh tokens for longer sessions. Never put sensitive data in JWT payload (it is base64-encoded, not encrypted).”}},{“@type”:”Question”,”name”:”How does the refresh token pattern work?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Short-lived JWT access tokens (15-60 minutes) + long-lived refresh tokens (7-30 days). Access token: used in the Authorization header for every API call. Stateless verification (no DB lookup). Expires quickly, limiting the window if stolen. Refresh token: an opaque random token stored in an HttpOnly cookie. Used only to request a new access token from the auth endpoint. Stored server-side (Redis or DB) — can be revoked. On logout: delete the refresh token from the server. On access token expiry: client sends refresh token → server validates it in Redis → issues new access token (and optionally rotates the refresh token). Refresh token rotation: each use of the refresh token issues a new one and invalidates the old. If an attacker steals a refresh token and uses it, the legitimate user's next use will be rejected (the old token is gone) — this detects theft. Rotate refresh tokens on every use for high-security applications.”}},{“@type”:”Question”,”name”:”How do you implement concurrent session management (max N devices)?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Track all active sessions per user: store a sorted set in Redis: ZADD user_sessions:{user_id} {created_at_epoch} {session_id}. On new login: ZADD the new session_id. ZCARD to count current sessions. If count > max_sessions: ZRANGE user_sessions:{user_id} 0 0 to get the oldest session_id. Delete it: DEL session:{oldest_session_id}. ZREM user_sessions:{user_id} {oldest_session_id}. The evicted session is invalidated immediately. Show users their active sessions (device, last IP, last active time) in account settings so they can revoke suspicious sessions. On logout: DEL session:{session_id}, ZREM user_sessions:{user_id} {session_id}. On "logout all devices": SMEMBERS → delete each session → delete the set.”}},{“@type”:”Question”,”name”:”What cookie security attributes prevent session hijacking and CSRF?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”HttpOnly: the cookie is not accessible via JavaScript (document.cookie). Prevents XSS attacks from stealing the session cookie — even if an attacker injects malicious JS, they cannot read the session cookie. Secure: the cookie is only sent over HTTPS connections. Prevents session ID interception on HTTP (man-in-the-middle attacks). SameSite=Strict: the cookie is not sent in cross-site requests. Prevents CSRF (Cross-Site Request Forgery): a malicious website cannot trick the user's browser into making authenticated requests to your API. SameSite=Lax allows the cookie for same-site navigation (GET requests) — use when cross-site navigation is needed (e.g., OAuth redirects). SameSite=None requires Secure=true. Set all three: HttpOnly; Secure; SameSite=Strict. Additionally: set a short TTL (30-60 minutes idle timeout, extend on activity), regenerate the session ID after login to prevent session fixation, and bind the session to IP/User-Agent for high-security applications (note: this breaks for mobile users).”}},{“@type”:”Question”,”name”:”How do you handle session expiry and idle timeout?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Two types of session expiry: (1) Absolute expiry: the session expires at a fixed time after creation, regardless of activity. Example: 8-hour session expires at 5pm even if the user is active. Used for compliance (banking: sessions must expire after N hours). (2) Idle timeout: the session expires after N minutes of inactivity. Refreshed on every authenticated request. Used for security (lock out inactive users). Implementation in Redis: idle timeout = EXPIRE with a rolling TTL. On each request: GET session:{session_id} (resets if using GETEX, or manually EXPIRE session:{session_id} 1800 after each access). Absolute expiry: store expires_at in the session data. On each request: check if datetime.now() > session.expires_at; if yes, delete session and redirect to login. Combine both: absolute_expiry from session data + Redis TTL for idle timeout. Redis TTL handles idle cleanup automatically; absolute expiry is checked in application code.”}}]}
Google system design covers authentication and session management. See common questions for Google interview: session management and authentication design.
Meta system design covers identity and session management. Review patterns for Meta interview: session management and identity system design.
Apple system design covers security and session management. See design patterns for Apple interview: session management and security design.