Redis Session Management#
oauth2-proxy's Redis session store separates session data from the browser cookie: the cookie carries only a signed ticket (a pointer + per-session encryption key), while the full SessionState is stored encrypted in Redis. This design keeps cookie size small and session data server-side, enabling distributed locking for safe token refresh.
Key packages:
| File | Role |
|---|---|
pkg/sessions/redis/redis_store.go | Top-level SessionStore: wraps a Redis client and implements persistence.Store |
pkg/sessions/persistence/manager.go | Manager: orchestrates ticket decode β load β save β lock wiring |
pkg/sessions/persistence/ticket.go | ticket struct: encodes/decodes cookie value, encrypts/decrypts session payload |
pkg/sessions/redis/lock.go | Redis-backed distributed lock via bsm/redislock |
pkg/middleware/stored_session.go | Middleware: drives lock-acquire β reload β refresh β save β release |
Cookie-to-Session Mapping (Ticket System)#
Each session is identified by a ticket stored as the cookie value. A ticket contains two parts :
- Ticket ID β a
{cookie-name}-{16-byte hex random}string, used as the Redis key. - Ticket secret β a random 16-byte AES key unique to this session.
The cookie value is encoded as v2.{base64(ticketID)}.{base64(secret)} and signed with the shared cookie-secret before being sent to the browser .
On each request, Manager.Load:
- Reads and validates the signed cookie.
- Decodes the ticket to extract the ID and per-session secret.
- Uses the ID to fetch ciphertext from Redis and the secret to decrypt it.
On save, Manager.Save reuses an existing ticket if one is present in the request cookie, or generates a new one. The ticket ID becomes the Redis key; TTL is set from --cookie-expire.
Encryption / Decryption#
Session data is encrypted with AES-GCM using the per-session ticket secret . This is distinct from the shared cookie-secret used only to sign the cookie carrying the ticket.
- Save path:
ticket.saveSessionencodes theSessionState(MessagePack + optional LZ4 compression, then AES-GCM), then callsStore.Saveto write the ciphertext to Redis with the configured TTL. - Load path:
ticket.loadSessionfetches ciphertext from Redis, reconstructs the AES-GCM cipher from the ticket secret, decodes the session, and attaches aLockobject toSessionState.Lock.
The Redis Store methods themselves are thin wrappers β Save, Load, Clear β that delegate directly to the Redis client with no additional transformation.
Distributed Locking for Token Refresh#
To prevent multiple concurrent requests from double-refreshing the same token (a critical issue with providers that rotate refresh tokens), oauth2-proxy uses a per-session distributed lock in Redis.
Lock key format: {ticketID}.lock
Lock implementation: redis.Lock wraps bsm/redislock and implements the sessions.Lock interface with four operations: Obtain, Peek, Refresh, Release.
The lock is wired during session load: ticket.loadSession calls the initLockFunc callback (bound to redis.SessionStore.Lock()) and assigns the result to sessionState.Lock.
Refresh flow in refreshSessionIfNeeded:
- Acquire β retry-loop calling
session.ObtainLockwith a 2-second lock TTL . Timeout after 5 seconds if the lock cannot be obtained . - Reload β once locked, re-fetch the session from Redis; if another goroutine already refreshed it, skip the provider call .
- Refresh & save β call the provider, update
CreatedAt, and persist viaStore.Save. - Release β deferred
session.ReleaseLockruns on exit regardless of outcome .
Both timeout constants have TODO comments noting they should become user-configurable .
Deployment Modes#
NewRedisSessionStore selects the client type based on flags ; --redis-use-sentinel and --redis-use-cluster are mutually exclusive :
| Mode | Flag | Client |
|---|---|---|
| Standalone | --redis-connection-url | redis.NewClient |
| Sentinel (HA) | --redis-use-sentinel=true + --redis-sentinel-connection-urls | redis.NewFailoverClient |
| Cluster | --redis-use-cluster=true + --redis-cluster-connection-urls | redis.NewClusterClient |
TLS is supported via --redis-ca-path and --redis-insecure-skip-tls-verify . The --redis-connection-idle-timeout must be set strictly less than the Redis server's own timeout setting (docs).
Configuration Quick-Reference#
Enable Redis with --session-store-type=redis. Cookie expiry (--cookie-expire, default 168h) sets Redis key TTL; --cookie-refresh controls how frequently sessions are refreshed (and the lock exercised). See the official session storage docs for the full option list.