“Stateless application” is often used to mean that any application node can handle the next request. It does not mean the system has no state. Identity, sessions, authorization, workflow progress, and durable business records still exist somewhere. Their placement determines routing, failure, expiry, revocation, and recovery behavior.
Session state is also not the same as business data. A shopping cart, fee ledger, or workflow record should not become node-local memory merely because a user has a session. Keep durable domain state in systems designed for its correctness and recovery requirements.
Begin with the session lifecycle
Before selecting a mechanism, define:
- what creates a session;
- which identity and authorization facts it represents;
- idle and absolute expiry;
- renewal behavior;
- logout and administrative revocation;
- key or credential rotation;
- behavior after a node or store failure;
- concurrent-session policy;
- audit and privacy requirements.
A mechanism that handles sign-in but has no answer for permission changes or account suspension is incomplete.
Option 1: node-local sessions
With local sessions, the server stores session data in its own memory or local persistence. The client holds an opaque session identifier, usually in a protected cookie. This is simple on one node: lookup is local and no shared session service is required.
Once several nodes serve requests, a session created on node A is unavailable on node B. Session affinity—also called sticky sessions—can route a client to the same backend using a cookie, source attribute, or load-balancer mapping. Affinity is a routing preference, not replication. If the node restarts, is removed during deployment, or becomes unhealthy, its in-memory sessions may be lost.
Affinity can also create uneven load when long-lived or busy sessions accumulate on particular nodes. Changes to the affinity key or load-balancer configuration can move clients unexpectedly. During autoscaling, draining must account for active sessions or accept reauthentication.
Local state can still be reasonable for a single-node internal tool, a deliberately disposable session, or a system where reauthentication is acceptable. Document that failure behavior. Do not present affinity as high availability unless the state itself has an appropriate recovery mechanism.
Option 2: a shared session store
A shared store lets any application node resolve an opaque session identifier. Redis, a database, or another key-value store can serve this role. Application nodes become easier to add, remove, and drain because session lookup no longer depends on the node that issued the session.
The store is now part of the authentication path. Define:
- availability and latency expectations;
- replication and failover behavior;
- persistence needs;
- capacity and eviction policy;
- idle and absolute expiry implementation;
- consistency during update and revocation;
- behavior when the store is unavailable.
Eviction deserves attention. A general-purpose cache configured to discard keys under memory pressure can turn active sessions into unexpected logouts. That may be acceptable, but it should be a conscious failure mode. If session records are security-sensitive, protect access, limit stored data, and avoid logging identifiers carelessly.
Expiry can be sliding, absolute, or both. Sliding expiry extends a session after activity; absolute expiry places a maximum lifetime regardless of activity. Refreshing expiry on every request may add write load, so some systems update it less frequently. The chosen optimization must not violate the intended security policy.
On logout or administrative revocation, deleting or marking the shared record can take effect on the next lookup. That gives shared sessions a straightforward revocation model, assuming every protected request actually checks the store and caches do not extend stale authorization.
When the store is unavailable, security-sensitive operations should generally fail closed: deny or defer rather than grant access without verification. The exact degraded behavior depends on risk. Public content may remain available while protected operations stop.
Option 3: signed claims
A signed token, often a JWT, carries claims that a service can verify using a cryptographic key without looking up a session on every request. This can reduce a central lookup and make verification convenient across services.
A signature provides integrity and issuer authentication when validation is correct. It does not encrypt the payload. Anyone holding a normal signed JWT can often decode its claims. Do not include secrets, credentials, or unnecessary personal data.
Validation must check more than the signature. Depending on the contract, verify the expected algorithm, issuer, audience, expiry (exp), not-before time (nbf), and any required subject or token type. Restrict accepted algorithms rather than trusting token input. Plan key rotation and how verifiers obtain and cache public keys.
Expiry limits how long a stolen or stale access token remains usable, but it does not by itself provide immediate logout. A token remains cryptographically valid until expiry unless the service consults revocation state, checks a user/session version, or uses another server-side control. Shorter lifetimes reduce the stale window but increase renewal frequency and do not eliminate theft risk.
Refresh tokens usually require stronger protection and often server-side state for rotation, reuse detection, revocation, or device/session management. A design described as “stateless JWT authentication” may still depend on an identity provider, key distribution, refresh-token records, and current authorization data.
Separate identity from authorization. A valid token can prove that a trusted issuer made claims at issuance time. It does not prove the account is still active, a role is still assigned, or access to a particular record is still allowed. For sensitive or rapidly changing permissions, check authoritative current state or use short-lived claims plus an appropriate invalidation strategy.
Cookies and tokens are different dimensions
A cookie is a browser storage and transport mechanism; a JWT is a token format. A browser can carry an opaque session ID in a cookie or a JWT in a cookie. JavaScript-accessible storage changes the exposure to script injection, while cookies require appropriate HttpOnly, Secure, and SameSite settings and cross-site request protections.
Do not choose “JWT versus cookie” as though they are direct alternatives. Decide first whether the server resolves opaque state or verifies self-contained claims, then choose a transport appropriate to the client and threat model.
Hybrid designs are normal
Many systems use a short-lived signed access token and a stateful refresh session. Others use opaque browser sessions at the edge and exchange signed service tokens internally. A gateway may authenticate a request and pass constrained identity context downstream, while each service still authorizes access to its own resources.
Hybrid designs can balance lookup cost and revocation needs, but they need one clear lifecycle. Avoid chains of tokens whose expiry and audience are poorly understood. Each hop should validate the credential intended for it; forwarding a broad user token everywhere increases exposure.
Plan for scale and failure
Session affinity may reduce cross-node lookup but complicates failover and balancing. Shared stores centralize session lifecycle but become critical dependencies. Signed claims distribute verification but make freshness and revocation harder. None is universally superior.
Test node removal, deployment draining, store timeout, key rotation, token expiry, permission removal, logout, and clock skew. Confirm that logs and traces can follow a session without exposing credentials. Monitor authentication failures by category so expiry is distinguishable from store outage or signature failure.
Capacity planning should include session count, record size, lookup rate, renewal writes, token size, and key-fetch behavior. Large tokens increase request bytes and can exceed proxy or cookie limits. A shared store with poorly distributed keys or aggressive expiry updates can become a hotspot.
For a broader assessment of trust boundaries, state ownership, and failure behavior, see the architecture review service.
Choose session placement from the required lifecycle. Local sessions trade simplicity for node dependence. Shared stores support centralized revocation and multi-node access while adding a critical dependency. Signed claims support local verification while requiring careful expiry, key management, and authorization freshness. The correct design states those tradeoffs explicitly.