Agents Playbook
Pillars/Security

Session Management Pattern

How to model + protect user sessions — login state, refresh, revocation, idle expiry, device binding.

Session Management Pattern

How to model + protect user sessions — login state, refresh, revocation, idle expiry, device binding.

TL;DR (human)

Sessions are how a user proves identity over time. Three knobs: where the session lives (cookie vs token), how long (idle + absolute timeout), and how it's revocable (server-side store vs stateless JWT). Each combination has trade-offs. For high-stakes products (banking, admin): server-side, short-lived, revocable. For consumer: longer, stateless OK with shorter idle.

For agents

Three session storage models

ModelStorageRevocationStateless?
Server-side sessionDB / Redis; cookie holds an opaque idServer deletes recordNo
Signed JWTToken holds claims; server verifies signatureHard (until expiry)Yes
HybridJWT short-lived; refresh token server-sideRefresh revocableMixed

Default: hybrid. Short-lived access JWTs (15-60 min); long-lived server-side refresh tokens (days-months); refresh tokens revocable on logout / compromise.

Why not pure JWT

Pure JWT (no server store):

  • Stateless = scales horizontally.
  • Cannot revoke before expiry without a denylist (which defeats statelessness).
  • Permissions captured at issuance; promotion / demotion mid-session not reflected until expiry.

For most products, the trade-off is wrong. Hybrid keeps statelessness for the hot path (access token) and revocability for the cold path (refresh).

CookieAuthorization header
Browser appsStandardRequires explicit handling
Mobile / CLI appsAwkwardStandard
CSRF riskYes (cookies sent automatically)No
XSS risk (token theft)Lower with HttpOnlyHigher
Cross-originCORS + SameSite configurationStandard

Web app: HttpOnly + Secure + SameSite=Lax cookies. CSRF protection via double-submit or origin header.

Mobile / API: Authorization: Bearer <token>. Token in secure storage (Keychain / Keystore).

Both: typical for products with web + mobile.

Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=...
  • HttpOnly: JS can't read; XSS doesn't steal.
  • Secure: HTTPS only; never in plaintext.
  • SameSite=Lax (or Strict for stricter): CSRF mitigation.
  • Path: scope.
  • Max-Age / Expires: lifetime.

Lifetime tuning

SettingDefaultHigh-stakes
Access token15-60 min5-15 min
Refresh token (idle)30 days1-7 days
Refresh token (absolute)90 days30 days
Idle timeout (no activity)30 days30 min
Step-up reauth (sensitive ops)10 min after action10 min

Display "last login" + active session list in UI. Let users revoke.

Refresh flow

1. Client has access (expired) + refresh (valid).
2. Client → POST /auth/refresh, body: {refreshToken}.
3. Server verifies refresh in store; rotates refresh; issues new access.
4. Client stores new access + new refresh.
5. Continue.

Rotation: each refresh issues a new refresh token; old one invalidated. Catches reuse:

If a previously-issued refresh token is presented after rotation,
the entire token family is revoked (suspected compromise).

Revocation paths

  • Logout: delete session from server-side store; cookie expires.
  • Forced revoke: admin tooling revokes by user / session / device.
  • Password change: invalidate all sessions for that user.
  • Suspicious activity detected: same.
  • Concurrent session limit: oldest session evicted (or new login blocked, with comms).

Device binding

Each session knows which device:

  • User-agent + IP at issuance.
  • Optional device-id (fingerprint or explicit registration).

UI surface: "Active sessions" with device + location + last activity, plus per-session revoke.

When a session is used from a new location / device: notify the user (email / app notification). Out-of-band confirmation for very sensitive operations.

Step-up authentication

For sensitive actions (password change, payment, admin operations):

  • Re-prompt for password / 2FA even if session is valid.
  • Token "step-up" stamp valid for short window (5-15 min).
  • Session NOT elevated permanently — step-up is per-operation.

Step-up is the difference between "logged in" and "authorised to do X right now".

Single sign-on (SSO)

Enterprise customers expect SSO:

  • SAML: enterprise IdPs (Okta, Azure AD, OneLogin).
  • OIDC: modern; OAuth-derived; supports SAML use cases.
  • SCIM: complementary; provisioning + deprovisioning.

Implementation:

  • Use a vetted library (do not hand-roll SAML).
  • IdP-initiated + SP-initiated flows both supported.
  • JIT (just-in-time) provisioning: first SSO login creates user.
  • Group → role mapping configured per tenant.

When user is deprovisioned in IdP: SCIM webhook revokes their sessions.

Session storage

Server-side sessions (Redis is common):

  • Key: session id.
  • Value: user id + metadata + permissions snapshot + expiry.
  • TTL: refresh token lifetime.

Scaling:

CSRF protection

Even with SameSite=Lax:

  • Double-submit cookie: server sends a CSRF token in both a cookie and the page; verifies they match on POST.
  • Origin / Referer header check (lightweight; less compatible).
  • Custom header pattern: same-origin XHR sends X-Requested-With; CORS prevents cross-origin from sending it.

Modern frameworks (Next.js, SvelteKit) ship CSRF middleware. Don't hand-roll.

Common failure modes

  • Pure JWT for sensitive product. Can't revoke; permission changes don't propagate. → Hybrid.
  • Long-lived JWT in cookie. XSS = forever compromised. → Short access + revocable refresh.
  • Refresh reuse without family revoke. Stolen refresh works forever. → Family-revoke on reuse.
  • No step-up. Long-running session changes a password without re-auth. → Step-up.
  • SSO without SCIM. Deprovisioned IdP user still has live sessions. → SCIM hook revokes.
  • Sessions never expire idle. Stolen laptop = forever access. → Idle timeout.
  • Concurrent session limit not enforced. One credential, infinite sessions. → Limit + comms.
  • Mobile app stores tokens in plain shared prefs / localStorage. Stolen device = stolen token. → Secure enclave / Keychain.

Tooling stack (typical)

ConcernTool
Auth libraryAuth.js / NextAuth, Clerk, Supabase Auth, Auth0, Cognito
Session storeRedis, DynamoDB, Postgres
SSO (SAML)passport-saml, @node-saml/passport-saml
SSO (OIDC)openid-client
SCIMscim-patch, vendor-supplied
Token rotation libraryjose (Node), pyjwt + custom rotation

See also