Sessions
A session is a logical conversation between a client and server, typically tracked by a unique identifier (session ID). Sessions allow servers to remember context (login state, cart, preferences) across otherwise stateless HTTP requests.
This file covers: 1. The session concept (stateful vs stateless). 2. Secure session management — best practices for tracking authenticated state safely. 3. Sticky sessions — load-balancer affinity for stateful servers.
1. Session Fundamentals
What is a Session?
- Definition: A logical conversation between client and server, often tracked by a unique identifier (session ID).
- Stateful vs Stateless: Stateful sessions store data server-side (e.g., user prefs in memory); stateless use tokens (e.g., JWT in client cookies).
- Why State Matters: Without affinity or shared storage, requests scatter across servers, losing context (e.g., cart items vanish).
Note: For the deeper distinction between stateful and stateless authentication (server-side session vs client-side JWT), see
Auth.md.
2. Secure Session Management
Session management securely tracks authenticated user state across requests, balancing usability and security.
Core Mechanics
- Session ID: Opaque, random string (e.g., 128-bit) as key to server-side data.
- Storage: Server-side (memory/Redis/DB); client-side cookie references it.
- Lifecycle: Create on login; expire on timeout/logout; regenerate ID periodically.
Best Practices
- Secure Cookies: HttpOnly (no JS access), Secure (HTTPS-only), SameSite=Lax/Strict (CSRF protection).
- ID Generation: Cryptographically secure random (e.g.,
secrets.token_urlsafe(32)in Python). - Binding: Tie to IP/user-agent (detect hijacking).
- Expiry: Sliding (extend on activity) or absolute; idle timeout (30min).
- Regeneration: On privilege change (e.g., login → admin).
- Storage: Redis for scale (TTL auto-expiry); encrypt sensitive data.
Python (Flask) Example:
from flask import Flask, session, request
app = Flask(__name__)
app.secret_key = 'strong-secret-key'
@app.route('/login')
def login():
session['user_id'] = 123 # Server-side storage
session.permanent = True # Persistent cookie
return 'Logged in'
@app.route('/profile')
def profile():
if 'user_id' not in session:
return 'Unauthorized', 401
return f'User {session["user_id"]}'
- Config:
session.permanent = True; regeneratesecret_key.
Threats Mitigated: Session fixation (regenerate ID), hijacking (binding/expiry).
3. Sticky Sessions (Session Affinity)
Sticky sessions, also known as session affinity or session persistence, is a load balancing technique that ensures subsequent requests from the same client are routed to the same backend server for the duration of a session. This maintains session state (e.g., user login, shopping cart) on one server, avoiding the need for shared storage. Introduced in early web load balancers (1990s), sticky sessions remain essential for stateful apps in microservices, despite alternatives like centralized sessions.
Load Balancing Basics
- Problem: Multiple servers (pool) handle traffic for scalability; balancer distributes requests.
- Without Sticky Sessions: Round-robin or least-connections → session data lost on server switch.
- With Sticky Sessions: Balancer "sticks" client to one server using a key (e.g., cookie, IP).
Affinity Key
- Definition: The identifier used to map client to server.
- Types:
| Key Type | How | Pros | Cons |
|---|---|---|---|
| Cookie-Based | Server sets cookie (e.g., JSESSIONID) with server ID. |
Accurate; works with NAT. | Client must accept cookies. |
| IP-Based (Source IP Hash) | Hash client IP to server. | No client changes. | Poor for NAT/shared IPs (all route to one server). |
| Header-Based | Custom header (e.g., X-Session-ID). |
Flexible. | App must set header. |
- Duration: Session cookie (expires on browser close) or persistent (fixed time, e.g., 30min).
Load Balancer Role
- Balancer Types: Hardware (F5), software (NGINX, HAProxy), cloud (AWS ALB, GCP LB).
- Mechanism: On request, balancer checks affinity key → routes to matching server; sets/updates key if new.
How Sticky Sessions Work (Step-by-Step)
- Initial Request: Client hits balancer (e.g., round-robin selects Server A).
- Server Assignment: Balancer notes affinity (e.g., sets cookie
server_id=A). - Session Start: Server A creates session data (e.g., stores cart in memory).
- Subsequent Requests: Client resends cookie/key; balancer routes back to Server A.
- Session End: Timeout, explicit logout, or cookie expiration → reassign.
Diagram:
Client ──(Request + No Cookie)──► Balancer ──► Server A (Assigns Cookie)
Client ──(Request + Cookie A)───► Balancer ──► Server A (Session Data)
Client ──(Request + Cookie A)───► Balancer ──► Server B? No → Server A
Implementation Examples
NGINX (Cookie-Based)
upstream backend {
server srv1.example.com;
server srv2.example.com;
sticky cookie srv_id expires=1h domain=.example.com path=/;
}
server {
location / {
proxy_pass http://backend;
}
}
- How: NGINX sets
srv_idcookie with server name; routes on match.
HAProxy (IP Hash)
backend webservers
balance source # IP hash
server srv1 192.168.1.10:80 check
server srv2 192.168.1.11:80 check
- How: Hashes client IP to consistent server.
Kubernetes Service (Session Affinity)
apiVersion: v1
kind: Service
spec:
sessionAffinity: ClientIP # IP-based
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800 # 3 hours
ports:
- port: 80
targetPort: 8080
- How: Kube-proxy sets affinity for traffic to Pods.
Pros and Cons
| Pros | Cons |
|---|---|
| Simplicity: No shared storage needed. | Uneven Load: Some servers overloaded if sticky. |
| Performance: Low latency (no session lookup). | Failover Issues: Session lost on server failure. |
| Compatibility: Works with legacy apps. | Scalability Limit: Can't freely add/remove servers mid-session. |
Alternatives to Sticky Sessions
- Centralized Sessions: Store in Redis/DB (e.g.,
session['cart'] = items); stateless servers. - JWT Tokens: Client-side state (signed, self-contained).
- Serverless: AWS Lambda uses stateless functions; sessions in DynamoDB.
- When to Switch: For horizontal scaling; sticky = quick fix for legacy.
Best Practices: - Use cookies over IP (handles NAT). - Set short timeouts (e.g., 30min) for security. - Monitor Affinity: Check load distribution; fallback to shared if uneven. - Graceful Failover: Use session replication or sticky with replication.