JWT Security: What’s Safe to Decode, and What You Should Never Paste Online
If you work with authentication, you decode JSON Web Tokens constantly — to see why a login failed, to check which claims a token carries, to confirm an expiry. It’s a routine debugging step. It’s also one where a careless habit can quietly hand someone a working credential. The fix isn’t to stop inspecting tokens; it’s to understand what a JWT actually is, so you know what’s harmless and what isn’t.
A JWT is Base64, not a secret
A JWT has three parts separated by dots: a header, a payload, and a signature. The header and payload are just Base64url-encoded JSON — not encrypted, not hashed, just encoded. That means anyone who has the token can decode it and read everything inside, with no key and no special tools. Paste the first two segments into any Base64 decoder and the claims pop right out.
This surprises people because the token looks scrambled, but “looks scrambled” isn’t security. (It’s the same confusion behind treating Base64 as protection — see why Base64 is not encryption.) Two consequences follow from this single fact, and they pull in opposite directions.
Consequence 1: reading a token is harmless — the token itself is not
Because the payload is public, decoding a JWT gives away nothing the holder didn’t already have. So a client-side JWT decoder that runs in your browser is perfectly safe to use — it just Base64-decodes locally.
The risk isn’t the decoding. It’s the token. An unexpired access token is a live key: present it to the right API and it authenticates you as that user. So the dangerous move is pasting a real, current token into a server-based online decoder, because now your credential has been transmitted to — and possibly logged by — someone else’s infrastructure.
The rule for inspecting tokens
Treat a live JWT like a password. Only paste tokens into a decoder that runs entirely in your browser, confirm in the Network tab that the value isn’t uploaded, and prefer expired or test tokens when you can. Never drop a production token into a random site to “just see what’s in it.”
Consequence 2: decoding tells you nothing about trust
There’s a second trap. Reading a token’s claims feels like inspecting it, but decoding and verifying are completely different operations.
- Decoding Base64-decodes the header and payload. It requires no key and proves nothing — a malicious user can edit the payload, re-encode it, and it will still “decode” cleanly.
- Verifying recomputes the signature using the secret (for HMAC-signed tokens) or the issuer’s public key (for RSA/ECDSA), and checks it matches. This is what proves the token was really issued by your auth server and hasn’t been tampered with.
A server must always verify, never just decode-and-trust. The classic JWT vulnerability is a backend that reads the claims without checking the signature — or, worse, honors an alg: none header. If your code makes an authorization decision based on a decoded payload it never verified, an attacker can forge any claims they like.
What belongs in a token (and what doesn’t)
Because the payload is readable by anyone holding the token, the contents are a privacy decision:
- Safe to include: a user ID, roles or scopes, the issuer, the expiry. Non-sensitive claims the client and server both need.
- Never include: passwords, API keys, full payment details, or any sensitive personal data. It’s all sitting there in plain, decodable Base64.
Keep tokens short-lived and pair a short access token with a refresh token, so that if one leaks it stops working quickly. If you genuinely need the contents to be secret in transit, that’s a different mechanism (an encrypted JWE), not a normal signed JWT. When you’re building or experimenting with tokens, a client-side JWT builder lets you construct and sign them without sending anything to a server.
Putting it together
The mental model is simple once you hold the two facts at the same time: a JWT’s contents are public, so reading one is cheap and safe — but the token is a live credential, so where you paste it matters, and whether you verified the signature is the only thing that determines trust.
Inspect tokens with a browser-based decoder, keep production tokens out of random websites, verify signatures on every request server-side, and never store a secret in a payload that anyone can decode. (For the bigger picture of how tokens fit into authentication, see JWT vs session cookies.)