A JWT (JSON Web Token) shows up in almost every modern authentication system, usually as a long string with two dots in it, sitting in a cookie or an Authorization header. Decoding one is trivial. Understanding what that decoding does — and doesn't — prove is where most confusion (and a few real vulnerabilities) come from.
What a JWT actually is
A JWT is three Base64URL-encoded segments joined by dots: header.payload.signature.
- Header — states the algorithm used to sign the token, e.g.
HS256orRS256. - Payload — the claims: user ID, expiry (
exp), issued-at (iat), roles, and whatever else the issuer put in. - Signature — a cryptographic signature over the header and payload, produced with a secret or private key the server holds.
Crucially, the header and payload are encoded, not encrypted. Base64URL is a reversible text encoding, not a cipher. Anyone holding the token — the browser, a proxy, an attacker who intercepts it — can decode the header and payload and read them in plain text without knowing any secret.
What decoding shows you
Paste a token into the JWT Decoder and it splits the token, Base64URL-decodes the header and payload, and shows you the resulting JSON. This is exactly what a server-side JWT library does internally before it even looks at the signature — decoding is a parsing step, not a trust decision.
This is genuinely useful for debugging: confirming which claims an auth provider actually issued, checking an exp timestamp against the current time, or verifying a role claim made it into the token as expected.
What decoding does not prove
This is the part that trips people up: decoding a JWT and reading a valid-looking payload tells you nothing about whether the token is genuine. Anyone can construct a JWT-shaped string with any payload they like — the header and payload are just Base64URL-encoded JSON, easy to fabricate by hand. The only thing that distinguishes a real token from a forged one is the signature, and checking a signature requires the issuer's secret or public key, which a decoding tool in your browser deliberately does not have (and shouldn't — you don't want to paste production signing secrets into a web page).
So: decoding is for reading and debugging. Verifying is a separate, server-side operation performed by whichever backend issued the token, using a JWT library that checks the signature against the secret or public key before trusting anything in the payload.
Common mistakes this leads to
- Trusting client-decoded claims for authorization. If a frontend decodes its own JWT and shows or hides UI based on a role claim, that's fine for UX — but the backend must independently verify the signature on every request that claim matters for. A user can edit their own token's payload freely; they just can't produce a valid signature for the edited version unless they have the signing key.
- Assuming "it decoded fine" means "it's valid." A decoder will happily decode an expired, revoked, or entirely fabricated token — decoding never fails due to an invalid signature, because it never checks one.
- Putting sensitive data in the payload. Since the payload is plainly readable by anyone holding the token, it should never contain passwords, secrets, or anything you wouldn't want visible in a debugger.
- Ignoring
alg: noneand algorithm-confusion attacks. Some JWT library misconfigurations historically accepted tokens with no signature at all, or let an attacker switch the expected algorithm. These are server-side verification bugs, not something a decoder can catch — another reason decoding and verifying are different jobs.
The short version
Decoding a JWT is safe, useful, and tells you exactly what claims are inside it. It never tells you whether those claims can be trusted — that requires signature verification with the correct key, performed server-side. Keep those two operations mentally separate and most JWT-related security mistakes disappear.