Reading a JWT without trusting it
A JWT is signed, not encrypted — anyone holding one can read it. Here is what the three segments actually contain, why decoding is not verifying, and the four checks a verifier has to make.
· 7 min read
A JSON Web Token is three chunks of Base64url separated by dots. The first two are plain JSON once decoded, and that is the single most misunderstood fact about them: a signed JWT is signed, not encrypted. Anyone who has the token can read every claim in it.
The three segments
The header says how the token is signed and, usually, which key signed it:
{ "alg": "RS256", "typ": "JWT", "kid": "k1" }
The payload is a bag of claims. Some are registered by the
specification — iss, sub, aud,
exp, nbf, iat, jti — and the
rest are whatever the issuer decided to include.
The signature covers the first two segments joined by a dot. It guarantees that nobody changed them. It guarantees nothing about confidentiality.
Decoding is not verifying
Decoding needs no key, so it tells you what a token claims and nothing about whether those claims are true. Verifying needs the issuer's key and answers a different question: did this issuer really mint this token, and is it still valid?
This is why the JWT Decoder here deliberately does not offer signature verification. A tool that asks you to paste a signing secret into a web page is teaching a habit that leaks secrets. Verification belongs in your application, against a key fetched from the issuer's JWKS endpoint.
The four checks a verifier must make
- The signature, against a key you fetched from the issuer —
not one embedded in the token. A
jkuorx5uheader pointing at a URL is an attacker-controlled input, not a key source. - The algorithm, pinned to what you expect. Accepting
whatever
algsays is how thealg: noneandRS256-to-HS256confusion attacks work: in the second, an attacker signs a token with your public key as an HMAC secret, and a library that trusts the header verifies it happily. - The expiry —
exp, andnbfif present. Allow a small clock skew, seconds not minutes. - The audience and issuer —
audmust name your API andissmust be the issuer you trust. A token minted for a different service by the same identity provider decodes perfectly and must still be rejected.
Practical consequences
Because the payload is readable, never put anything sensitive in a claim. If you must, use JWE, which actually encrypts.
Because a bearer token is a credential, treat any token you have pasted into a tool you do not control as disclosed, and rotate it. That is also why this site's decoder runs entirely in your browser and is excluded from the recently-used list — you can confirm both in your network tab and your local storage.
Because tokens cannot be revoked once issued, keep lifetimes short. A token with a year of validity is a password you cannot change. If you need revocation, you need a token introspection endpoint or a short-lived access token paired with a refresh token.
When a valid-looking token is rejected
In rough order of likelihood: exp has passed (check against the
server's clock, not yours); aud names a different service;
iss is a different tenant of the same provider; the
kid refers to a key that has been rotated out of the JWKS; or the
token was truncated in transit — an Authorization header that has
passed through a proxy with a header size limit is a classic.
Tools mentioned in this guide
JWT Decoder
API
Decode a JWT header and payload, and check expiry and standard claims.
Runs in your browser
Base64 Decoder
Encoding
Decode Base64 back to text or download it as a file.
Runs in your browser
Unix Timestamp Converter
Developer
Convert a Unix timestamp to a human date and back, in any time zone.
Runs in your browser