Skip to main content

What's Actually Inside a JWT: Header, Payload, Signature

A JWT is not encrypted. It is three pieces of Base64URL text joined by dots, and anyone holding the token can read the first two without a key of any kind — which is the single most important thing to understand about it.

Three segments, two dots

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9   <- header
.
eyJzdWIiOiIxIiwibmFtZSI6IkFkYSJ9       <- payload
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV…   <- signature

Each segment is Base64URL — the ordinary Base64 alphabet with + and / swapped for - and _ so the result is safe in a URL, and the = padding usually dropped.

What each segment holds

SegmentHoldsReadable without a key?
HeaderHow the token is signed — alg, typ, and often kid to name the key.Yes, by anyone holding the token.
PayloadThe claims: who the subject is, when it expires, who issued it, plus anything custom.Yes, by anyone holding the token.
SignatureBytes proving the first two segments were not altered by someone without the key.Yes, but meaningless without verifying it against the key.

Encoded is not encrypted

Base64URL is an encoding, not encryption. It exists to make bytes safe in a URL, and reversing it needs nothing but the token. Anything in the payload should be treated as public — a JWT is the wrong place for anything you would not print in a log.

The signature does not hide the contents either. It makes tampering detectable — change one character of the payload and the signature no longer matches — but it does nothing to conceal what is written there.

The registered claims

ClaimNameMeaning
issIssuerWho created the token.
subSubjectWho or what the token is about, often a user id.
audAudienceWho the token is intended for.
expExpirationSeconds since the epoch, after which it should be rejected.
nbfNot beforeSeconds since the epoch, before which it should be rejected.
iatIssued atWhen it was created.
jtiJWT IDA unique identifier, used to prevent replay.

These names are reserved so that different systems agree on what they mean. Everything else in the payload is a custom claim — roles, tenant ids, feature flags — and carries no standard meaning beyond what your own code gives it.

Decoding versus verifying

Decoding reads the first two segments. Verification recomputes the signature with the key and compares it. They are separate operations, and only the second establishes that a token is genuine — which is why a decoder alone can never tell you a token is trustworthy.

This is the distinction behind most JWT security advice. Reading a claim to decide what to display is fine; reading a claim to decide what someone is allowed to do is not, unless the signature has been verified first.

Look inside a token

Paste one into the JWT Decoder to see the header and payload as JSON, with the time claims rendered as readable timestamps.

It decodes and does not verify — the signature is never checked, and the output says so on every result. Decoding runs in your browser, which matters here: a real token is a live credential, and this one is not sent anywhere.

Related

For time claims specifically — JWT expired covers exp, Unix time, and clock skew. If a token will not decode at all, why a JWT will not decode covers segment and encoding failures.

Measured with

  • DataToolsHQ JWT Decodernative decoding (no verification)