Authentication at Scale
How identity survives horizontal scaling â stateful server sessions vs stateless JWTs, OAuth2/OIDC single sign-on, and the access-token + refresh-token hybrid that balances scale with revocation.
Once a system runs on many servers, where you keep auth state becomes an architectural decision. Server sessions keep state centrally (simple, revocable, but needs a shared store). JWTs make each token self-validating (scales effortlessly, but hard to revoke). Production systems usually combine short-lived JWTs with revocable refresh tokens behind OAuth2/OIDC.
Authentication answers 'who are you?' (login). Authorization answers 'what may you do?' (roles/permissions). After login the server issues a credential the client presents on every later request so it doesn't re-authenticate each time.
The server stores session state and hands the client an opaque session-id cookie. On each request it looks up that id in a store to find the user. Simple and instantly revocable (delete the row) â but with many app servers the session store must be shared (Redis), adding a dependency and a lookup hop.
The server signs a token containing the user's claims. Any server can verify the signature locally and trust the claims â no shared store, no lookup. This scales out beautifully. The trade-off: a signed token can't be un-signed, so revocation before expiry is hard.
Real systems delegate login to an identity provider via OAuth2/OpenID Connect (SSO), which returns tokens. To balance scale and revocation, they use short-lived access tokens (JWT, ~15min) plus a long-lived refresh token that IS checked against a store â so you get stateless validation on the hot path and revocation where it counts.
Key Concepts
Auth state lives on the server, keyed by an opaque cookie. Instantly revocable; needs a shared store to scale out.
A signed token carrying the user's claims. Verified locally with no store lookup â ideal for horizontal scale.
A short-lived access token (used per request) plus a long-lived refresh token (exchanged for new access tokens, revocable).
Delegated auth: an identity provider authenticates the user and issues tokens, enabling SSO across apps.
Invalidating a credential before expiry. Trivial for sessions, hard for pure JWTs â hence short TTLs + refresh.
Pinning a user to one server so in-memory session data survives â a crutch that a shared store or JWT removes.
1// Stateless JWT: verify locally on every server, no store lookup.2const token = jwt.sign(3 { sub: user.id, role: user.role },4 PRIVATE_KEY,5 { algorithm: "RS256", expiresIn: "15m" } // short-lived!6);78// Any app server validates with the PUBLIC key â zero shared state:9const claims = jwt.verify(bearer, PUBLIC_KEY, { algorithms: ["RS256"] });1011// The catch: you can't un-sign a token. Solve revocation with12// SHORT access tokens + a refresh token you CAN revoke server-side:13app.post("/refresh", async (req, res) => {14 const rt = req.cookies.refresh;15 if (await isRevoked(rt)) return res.sendStatus(401); // checked store16 res.json({ access: signAccess(rt.user), expiresIn: "15m" });17});
Auth touches every request and every security incident. The session-vs-token trade-off (statefulness, revocation, scale) is a classic interview question, and getting it wrong causes real bugs â random logouts when scaling, or tokens that outlive a logout. Understanding OAuth2/OIDC and the refresh-token pattern is essential for any multi-server system.
Common Pitfalls
1Logout Doesn't Log Out
A security team reports that after a user clicks 'log out' (or an admin disables an account), the old JWT keeps working for up to an hour until it expires.
Pure JWTs are stateless and self-validating â the server never checks a store, so there's nothing to flip to invalidate an outstanding token. Long token lifetimes made the window dangerous.
Shorten access-token TTL to ~15 minutes and move long-term sessions to a revocable refresh token checked against a store. On logout/disable, revoke the refresh token (and optionally maintain a small deny-list of access-token jti values). The next refresh fails, closing the window.
Takeaway: Stateless tokens trade revocability for scale. Short access tokens + a revocable refresh token restore control without putting a store lookup on every request.
2Sessions Break When You Scale Out
An app that ran fine on one server starts logging users out randomly after the team scales to four instances behind a load balancer.
Sessions were stored in each server's local memory. The load balancer sent a user's next request to a different instance that had never seen their session, so it looked like they weren't logged in.
Move session storage to a shared external store (Redis) that every instance reads, so any server can validate any session. (Sticky sessions are a weaker fallback.) Alternatively switch to stateless JWTs to remove the shared dependency entirely.
Takeaway: In-memory sessions are invisible-state that breaks horizontal scaling. Either externalize session state to a shared store or go stateless with tokens â never assume requests return to the same instance.
Join the discussion
Comments
Loading comments...
Cookie preferences
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.