How to read a JSON Web Token without leaking it
A JWT looks like gibberish until you know it is three base64url segments with a specific meaning each. Once you can read one, a surprising number of authentication problems become obvious.
By Enrique M. · 6 min read · Last reviewed 2026-10-11
Three segments, three meanings
A signed JWT is three base64url strings separated by dots: the header, the payload and the signature. The first two are encoded, not encrypted, so anyone holding the token can read them. Only the third segment is a cryptographic artefact.
The header usually names the algorithm and, increasingly, the key identifier. The payload carries the claims: who the token is for, who issued it, when it expires and whatever the application decided to put there. The signature is a hash computed over the first two segments using a key the client does not have.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMifQ.c2lnbmF0dXJl - `header` — base64url of a JSON object describing the algorithm
- `payload` — base64url of a JSON object holding the claims
- `signature` — base64url of the signature over the first two segments
Why the payload is not private
Base64 is an encoding, not encryption. The payload is decoded by any client that receives the token, and by every logging layer, proxy and analytics script that sees the Authorization header along the way.
This has a direct consequence for what you put in a token. A claim that a server treats as authoritative but that the client can also forge is a broken control. If a decision depends on a claim, that claim has to be covered by the signature and verified on every request.
- Safe inside a signed token: identifiers, roles, scopes, issued-at, expiry
- Never inside a token: secrets, personal data you do not have a legal basis to hold
- Never trusted without verification: anything, because the client can craft its own token
The claims that decide whether a token is usable
Three registered claims cause most of the practical failures. `exp` is the expiry, `nbf` is the earliest valid moment and `aud` is the audience — which service the token was minted for.
A token that is correctly signed for one service and presented to another is a real and common bug. Checking the audience is what stops a token issued for the internal API from being replayed against the public one.
- `iss` — who issued the token
- `sub` — the subject, usually a user id
- `aud` — who the token is for; a string or an array
- `exp` — expiry, as a Unix timestamp in seconds
- `iat` — when it was issued
- `jti` — a unique id, used for revocation lists
What to check when a request is rejected
When an API returns 401, the token is usually missing, malformed, expired or signed by the wrong key. When it returns 403, the token was accepted and the operation was not permitted. The distinction matters: one is a credential problem and the other is an authorisation problem.
Decode the token and look at the expiry first, then the audience, then the issuer. A token that expired two minutes ago explains most of these incidents in one glance.
What to take away
- The header and payload are readable by anyone holding the token, so treat them as public.
- A signed token is not proof until the signature has been verified against the issuer key.
- Check `exp`, `aud` and `iss` before looking anywhere else when a request is rejected.
- Never paste a live token into a web page you do not control, including this one.