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; regenerate secret_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)

  1. Initial Request: Client hits balancer (e.g., round-robin selects Server A).
  2. Server Assignment: Balancer notes affinity (e.g., sets cookie server_id=A).
  3. Session Start: Server A creates session data (e.g., stores cart in memory).
  4. Subsequent Requests: Client resends cookie/key; balancer routes back to Server A.
  5. 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_id cookie 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.