security · 7 min read

What a JWT is, and what it is not

A long string with two dots in it, and the middle section is a readable JSON document that anyone holding the token can print.

A JSON Web Token is a way of carrying facts that a recipient can verify without asking anybody. That is the whole idea, and both halves of it matter: the facts are visible, and the verification is offline.

The three parts

Split on the dots and you get a header, a payload and a signature.

The header names the signing algorithm. The payload is a JSON object of claims. Both are Base64, in the URL-safe variant — which is encoding, not encryption, so both are plainly readable by anyone with the token.

The signature is computed over the first two parts using a secret or a private key. Change one character of the payload and the signature no longer matches.

What the signature actually proves

That the token was issued by someone holding the key, and that it has not been modified since.

It proves nothing about who is presenting it. A stolen token is a valid token — the signature is just as good in an attacker's hands, which is why a JWT is a bearer credential and has to be protected like a password.

And it does not hide anything. This is the part that catches people: signing and encrypting are different operations, and a standard JWT does only the first.

The claims worth knowing

exp — expiry, as a Unix timestamp in seconds. The one you check first when a token is rejected.

iat — issued at. nbf — not valid before.

sub — the subject, usually the user identifier. iss — who issued it. aud — who it is for.

That last pair matters more than it looks. A service that verifies the signature but ignores the audience will accept a valid token issued for a different service entirely.

Why they are used

A traditional session identifier is a random string that means nothing on its own, so every request costs a database lookup to find out who it belongs to.

A JWT carries the answer with it. Any service holding the key can verify it and read the user id without asking anyone — which is why they suit distributed systems and anything crossing service boundaries.

The cost, which is revocation

The server keeps no record of the token, so there is nothing to delete when you want it to stop working.

A user changes their password, an employee leaves, a token is stolen — and every issued token stays valid until it expires. Nothing about the format allows otherwise.

The usual answers all give something back. Short expiry with refresh tokens narrows the window and adds a second credential to manage. A denylist works and reintroduces the per-request lookup the format was chosen to avoid. A version number on the user record, checked against a claim, is the same trade in a smaller form.

This is the genuine trade-off, and it is worth making deliberately rather than discovering during an incident.

The mistakes that recur

Trusting the header. The token states its own algorithm, and a verifier that believes it can be handed alg: none and asked to accept an unsigned token. Pin the algorithm on the server side.

Putting sensitive data in the payload. Email addresses, names, internal identifiers — all published to anyone holding the token.

Storing tokens in local storage. Readable by any script on the page, which makes one cross-site scripting flaw into full account takeover. An http-only cookie is not reachable from JavaScript.

Never expiring. A token without exp is a permanent credential you cannot withdraw.

Weak secrets. A symmetric key that is a dictionary word can be recovered offline from a single token, and then anyone can mint their own.

Reading one

Decoding a token shows the header and payload immediately, which is the fastest way to answer "why was this rejected" — the expiry, the audience and the issuer are usually the whole story.

Do it locally. A token is a live credential, and pasting one into a website means handing that site an authenticated session. Decoding is Base64 and JSON parsing, which needs no server, so a decoder that sends the token somewhere is asking for something it does not need.

The short version

Signed, not encrypted: the payload is public to anyone holding the token. The signature proves it was not altered and says nothing about who is carrying it. And you cannot revoke one without giving up the property that made it attractive.

Questions

What is a JWT token?

A string in three parts separated by dots: a header saying how it was signed, a payload of claims, and a signature. The first two are Base64 and readable by anyone. The signature proves the other two have not been altered by someone without the key.

Is a JWT encrypted?

Not by default. A standard JWT is signed, not encrypted, so the payload is public to anyone holding the token — paste one into a decoder and every claim is visible. There is an encrypted variant, JWE, and it is much less commonly used.

Is it safe to put user data in a JWT?

Only data you are willing to publish to whoever holds the token. A user id and a role are normal. An email address is a disclosure. Anything sensitive does not belong in a signed token, because signing prevents tampering and does nothing about reading.

Why can’t I log a user out of a JWT?

Because the server does not store it. A JWT is valid because its signature checks out and its expiry has not passed, so there is nothing to delete. Revocation needs a denylist the server checks on every request — which reintroduces the lookup the format was chosen to avoid.

What does exp mean in a JWT?

The expiry, as a Unix timestamp in seconds. A token rejected as expired is worth decoding to see what exp actually says: a clock skew of a few seconds between two servers is a common cause, and the claim is often the fastest way to find it.

Tools from this guide