JWTs: What They Prove, and the Mistakes That Undo It

Signed, not encrypted. Readable by anyone holding one, and hard to revoke — which decides where they belong.

A JSON Web Token is three base64url segments separated by dots: header, payload, signature. It is signed, not encrypted. Anyone holding a JWT can read every claim inside it — paste one into any decoder and the payload appears in full.

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMiLCJleHAiOjE3MDB9.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

That is the single most misunderstood fact about them. Never put anything in a JWT you would not hand to the bearer.

What the signature actually proves

That the token was issued by someone holding the signing key and has not been altered since. That is all. It says nothing about whether the token is still valid — whether the user has logged out, been deleted, or had permissions revoked. A signature cannot expire; only a claim inside can, and only if you check it.

The failure modes, in order of severity

The revocation problem

The appeal of JWTs is that a server can verify one without touching a database — the signature is self-sufficient. That is also the drawback: you cannot un-issue a token. A user logs out, you delete their session, and their JWT keeps working until it expires, because nothing checks anything.

Every mitigation gives back some of the benefit:

Where they fit, and where they do not

JWTs earn their keep when verification must happen without shared state: several services validating the same token, or an API gateway checking a request before routing it. They are a poor fit for a single conventional web application, where a session cookie backed by server-side storage gives instant revocation, smaller requests, and far fewer ways to get it wrong.

"We use JWTs for sessions" is usually a decision inherited from a tutorial rather than made. If everything that validates the token can reach the same database anyway, the token is doing no work that a session ID would not do better.

Storage in a browser

localStorage is readable by any script on the page, so one XSS is one stolen token. An HttpOnly, Secure, SameSite cookie is not readable by script, at the cost of needing CSRF protection. The second is the better default, and the fact that it looks less like "stateless auth" is not a technical argument.

Decode a JWT → · Related: hashing · Back to all articles