Security 5 min read · 15 August 2026

What a JWT is, how to read one, and why it is not encrypted

A JWT is signed, not hidden. Anyone holding one can read every claim inside it — which is fine, expected, and the source of the most common mistake people make with them.

A JSON Web Token is a compact, signed statement that something is true — typically "this is user 4821, and they are an administrator, and this expires at 14:32". It is signed so the recipient can verify nobody altered it. It is not secret, and that is the single most important thing to understand about it.

Three parts, separated by dots

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiI0ODIxIiwibmFtZSI6IkpvIiwiZXhwIjoxNzM1Njg5NjAwfQ
.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

Header — which algorithm signed it, and that this is a JWT.

Payload — the claims. Standard ones have short names: sub (subject, usually the user id), exp (expiry, as a Unix timestamp), iat (issued at), iss (issuer), aud (audience). You can add your own.

Signature — the header and payload, signed with a key.

The first two parts are Base64url-encoded JSON, not encrypted. Encoding, not encryption — the difference matters. Anyone holding the token can decode and read every claim, with no key and no effort. Our JWT decoder does it in the browser, and so does any Base64 decoder if you split on the dots yourself.

So: never put anything sensitive in a JWT payload. No passwords, no card details, no personal data you would not print on a postcard. The token is a claim to be verified, not a container to be protected.

What the signature actually guarantees

It guarantees the token was issued by someone holding the key and has not been modified since. Nothing else.

Two families of algorithm do this. HMAC (HS256 and friends) uses one shared secret to both sign and verify — simple, fast, and it means every service that can verify a token can also mint one. RSA and ECDSA (RS256, ES256) sign with a private key and verify with a public one, so you can distribute verification widely while only the issuer can create tokens. For anything beyond a single application, the asymmetric option is usually the right one.

Change one character of the payload and the signature no longer matches, so the token is rejected. That is the whole security model, and it depends entirely on the verification step being done correctly.

The classic vulnerability

The header declares the algorithm — and naive implementations trust it.

An attacker takes a valid token, edits the payload to say "admin": true, sets the header algorithm to none, and removes the signature. A library that reads the algorithm from the token and dutifully applies "no verification" accepts it.

A related attack: a system expecting RS256 receives a token claiming HS256, signed with the RSA public key as if it were an HMAC secret. The public key is public, so the attacker can do this — and a library that picks its verification method from the header will verify it successfully.

The fix in both cases is the same: the application decides the algorithm, not the token. Configure the expected algorithm explicitly and reject anything else before verifying. Current libraries default to this; older ones and hand-rolled code often do not.

Expiry, and the problem with it

The exp claim is not advisory. Verification must check it, and a surprising number of custom implementations decode the token, validate the signature, and never look at the timestamp.

Which leads to the genuine architectural weakness of JWTs: a signed token cannot be revoked. A session in a database is deleted and it is gone. A JWT is valid until it expires, because verification is purely mathematical and consults nothing.

Log a user out and their token still works. Change their permissions and the old token still claims the old ones. Disable an account and its token remains valid until expiry.

The usual mitigations, each with a cost:

  • Short expiry — 5 to 15 minutes, with a longer-lived refresh token that is stored server-side and can be revoked. This is the standard pattern.
  • A denylist of revoked token ids, checked on each request. It works, and it reintroduces the server-side lookup that JWTs were supposed to avoid.
  • A token version per user, incremented on logout or permission change, included as a claim and compared. Same trade.

Notice that all three erode the main selling point. Which is the honest summary of JWTs: stateless verification is genuinely useful, and the moment you need revocation you are paying for state again.

Storing them in a browser

Both options have a real weakness and there is no third option.

localStorage is readable by any JavaScript on the page — so any successful XSS, including one in a third-party script you loaded, exfiltrates the token.

An httpOnly cookie is unreadable by JavaScript, which removes that risk, and it is sent automatically with requests, which introduces CSRF. That is mitigated with SameSite=Strict or Lax plus a CSRF token.

The consensus has moved toward httpOnly cookies with SameSite, because CSRF has well-understood defences and XSS token theft is silent and total. Neither is a substitute for not having XSS.

When not to use one

JWTs became a default answer to a question many applications were not asking. For a conventional server-rendered web application with a session cookie and a database, sessions are simpler, revocable immediately, and smaller on the wire. Switching to JWTs there adds complexity and removes a feature you had.

They earn their place when verification must happen somewhere that cannot reach your session store: multiple independent services, an API consumed by third parties, a mobile client talking to several backends, or a gateway authorising requests without a round trip.

A checklist

  • Verify the signature. Always, before reading any claim.
  • Pin the expected algorithm in configuration; reject anything else.
  • Check exp, and check iss and aud if you issue tokens for more than one audience.
  • Keep expiry short and pair it with a revocable refresh token.
  • Put nothing sensitive in the payload.
  • Use HTTPS everywhere — a token is a bearer credential, and anyone who intercepts it becomes that user.
  • Use a maintained library. This is not a good place to be original.

More guides

Stay Updated

Get the latest tools, AI features, and product updates. No spam.