# JWT for Sessions (Anti-Pattern)

A pattern, popular in JavaScript bootcamp curricula and tutorial-driven learning, of using JSON Web Tokens (JWT, RFC 7519) as the user-session credential. After login, the server returns a JWT containing the user's identity claims; the client stores it (often in `localStorage`) and sends it on each request, usually as `Authorization: Bearer <token>`. The server validates the JWT signature on each request without looking anything up. This is sold as "stateless authentication."

Several long-running write-ups (joepie91 2016, Paragon Initiative 2017, [[samsch-jwt-auth-gist|samsch gist]]) make a converging case that this is the wrong tool. The standard alternative is [[cookie-session-auth|regular cookie sessions]].

## Why it doesn't fit

**Lifetime mismatch.** JWTs are designed for very short-lived tokens (~5 minutes or less). Sessions need longer lifespans — hours, days, sometimes weeks. Stretching the lifetime turns every flaw of the design into a liability that grows with time on the wire.

**Revocation is a hard add-on.** A signature-only validation pipeline has no way to invalidate a token before its `exp`. Real systems need to log users out, kick a compromised account, or roll an admin's permissions in real time. Bolting revocation onto JWTs means a server-side blocklist that has to be checked on every request — at which point the "stateless" property is gone and the JWT is no longer doing anything a session ID couldn't do better.

**Statelessness is a lie at any reasonable security level.** Real auth needs revocation, key rotation, replay protection, and a way to invalidate sessions on password change. All of these need server state. The argument from samsch (and joepie91) is that once you accept you need a data store, store everything in it and just hand out an opaque session ID. The JWT only buys you payload-on-the-wire, which costs more bytes and gains nothing.

**The JWT/JOSE spec family has a poor security track record.** The original spec allowed `alg: none` (an unsigned JWT that says "trust me"); some libraries shipped this as the default. Multiple critique-pieces detail confusion between symmetric/asymmetric algorithms, key-confusion attacks, and footguns in the broader JOSE family. Paragonie's "JWT is a Bad Standard" is the canonical takedown.

**Bigger and slower.** A JWT carrying only a session-ID claim is larger than a session cookie, must be parsed and signature-verified on every request, and gains nothing.

## Why people reach for it anyway

- It's the default in many JavaScript and Node.js tutorials.
- "Stateless" sounds appealing for horizontal scaling — until you realize you need a Redis/Postgres for revocation either way.
- Mobile and SPA developers were told cookies are old-fashioned and Bearer tokens are modern. They aren't; cookies (specifically `HttpOnly; Secure; SameSite=Strict|Lax`) are the right tool for browser auth and work perfectly well for the SPA case.
- Microservice architectures where one service authenticates and others verify without a shared session store. JWT works here, but this is the *between-services* case, not the *user-session* case.

## Where JWTs (or [[paseto]]) actually fit

The same critics agree there are legitimate uses for signed short-lived tokens, all of them at the ~5-minute end of the lifetime spectrum:

- Single-sign-on transports (the Google pattern) — log in at host A, hand a JWT to host B as the once-used proof, host B mints its own session.
- Password-reset and email-verification links.
- Capability tokens (signed download URLs, signed action tokens).
- Service-to-service auth in microservices.

Even for these, [[paseto]] is now considered a better-designed alternative.

## Don't pair the anti-pattern with localStorage

Storing JWTs in `localStorage` or `sessionStorage` makes any XSS into a credential exfiltration. Browser JS can read every entry. `HttpOnly` cookies cannot be touched by JS at all and survive XSS. The 2018 Randall Degges post linked from the samsch gist is the standard cite.

## Related

- [[cookie-session-auth]] — the actual recommended pattern for browser sessions
- [[paseto]] — the better-designed short-lived-token spec for the cases where you do need one
- [[samsch-jwt-auth-gist]] — the link-friendly summary version of this case
