Skip to content

Developer Tools

What Is Actually Inside a JWT

A JWT is three chunks of Base64, and the first two are readable by anyone holding the token. What the signature protects, what the claims mean, and why alg:none was a real vulnerability.

4 min read

A JWT is three chunks of Base64 separated by dots. That’s it. It looks encrypted and it isn’t — anyone holding the token can read everything in it, right now, without a key.

That single fact is the source of most JWT mistakes, so it’s worth being concrete about.

The three parts

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

Part Contains Readable by anyone?
Header The signing algorithm and, often, a key ID Yes
Payload The claims — who the user is, when it expires, what they can do Yes
Signature Proof the first two parts weren’t altered Yes, but meaningless without the key

The first two are Base64url-encoded JSON. Base64 is an encoding, not encryption. It exists so binary data survives a trip through something that only handles text. Decoding it needs no secret and takes microseconds.

What signing actually protects

The signature answers one question: has this token been modified since it was issued? Change a single character of the payload and the signature no longer matches.

It answers no other question. In particular it does not hide anything. If you put an email address, an internal user ID, a role name or a phone number in the payload, everyone who ever holds that token can read all of it.

Put nothing in a JWT payload that you wouldn’t put in a URL.

The claims worth knowing

Some claim names are standardised. These are the ones you’ll actually meet:

Claim Name What it’s for
iss Issuer Who minted the token
sub Subject Who it’s about — usually a user ID
aud Audience Which service is meant to accept it
exp Expiry Unix seconds after which it must be rejected
nbf Not before Unix seconds before which it must be rejected
iat Issued at Unix seconds when it was created
jti JWT ID A unique ID, so a token can be revoked individually

The three time claims are all Unix timestamps in seconds. Almost every JavaScript date API works in milliseconds. That mismatch is behind a good share of “my token expires immediately” and “my token never expires” bugs — see why your Unix timestamp is 1000× off.

Reading one safely

Debugging a token means pasting it somewhere. A production token is a live credential: whoever receives it can act as that user until it expires.

So the only safe place to paste one is a decoder that doesn’t transmit it. Our JWT Decoder runs entirely in the page — nothing is sent anywhere, and it deliberately stores nothing, not even locally, because a token is exactly the kind of thing that shouldn’t be sitting in your browser storage after you’ve finished.

It also flags the algorithm, which brings us to the interesting failure.

The alg: none problem

The header names the signing algorithm. Early libraries trusted it. So an attacker could take a valid token, change the payload to say they were an administrator, set "alg": "none", delete the signature, and send it. A library that read none and dutifully skipped verification let it straight through.

Modern libraries reject it, but the shape of the lesson survives: the server decides which algorithm is acceptable, not the token. A verifier that reads alg from the token and does what it’s told is trusting input it’s meant to be checking.

The related version swaps RS256 for HS256 — asymmetric for symmetric — so the library uses the public key as an HMAC secret. The public key is public. Same lesson.

Expiry is a rejection rule, not a delete button

A token past its exp doesn’t stop existing. It’s just supposed to be refused. That means:

  • A stolen token works until it expires, which is why short lifetimes matter more than they feel like they should.
  • There’s no built-in revocation. Logging out doesn’t invalidate a JWT unless you keep a deny-list, which is a piece of server state — the thing JWTs were meant to avoid.
  • Clock skew between two servers can reject a valid token or accept an expired one. A few seconds of leeway is normal; a few minutes hides real problems.

The short version

Three Base64 chunks. The first two are public. The signature proves nothing was changed and hides nothing. Times are in seconds. Never trust the header’s algorithm claim, and never paste a live token into something that uploads it.

Tools in this guide

All of them run in your browser. Nothing uploaded.

Last updated: September 4, 2026