JSON Web Tokens are everywhere in authentication, and their apparent simplicity hides a set of sharp edges: tokens that decode fine but prove nothing, payloads that anyone can read, and configuration mistakes that have led to some of the most infamous API breaches of the last decade.
What the three segments actually are
A JWT is three Base64URL-encoded segments joined by dots:
eyJhbGciOiJIUzI1NiJ9 . eyJzdWIiOiIxMjMifQ . SflKxwRJSme...
header payload signature
- Header — a JSON object naming the algorithm (
alg) and token type. NoteHS256means HMAC-SHA256 (symmetric), whileRS256/ES256are asymmetric signatures. - Payload — the claims:
sub(subject),exp(expiry),iss(issuer),aud(audience), plus whatever your app adds. - Signature — the proof that the header and payload were signed by the holder of the secret or private key.
Paste any token into our JWT Decoder and you can read all three — locally, in your browser.
Decoding is not verification (the trap that produces breaches)
Decoding is Base64URL plus JSON parsing — anyone can do it, and reading a payload is the intended use of the decoder. Verification is different: it recomputes the signature from the received header and payload and checks that it matches, then validates the claims (exp, nbf, iss, aud). Every one of the classic JWT vulnerabilities comes from skipping one of these steps:
alg: none— ancient libraries accepted unsigned tokens if the header said so. Servers must pin the algorithms they expect.- Algorithm confusion — a server configured for RS256 that accepts a token claiming HS256 lets an attacker sign with the public key as the HMAC secret. Pin algorithms server-side; never trust the header’s
alg. - Missing expiry checks — a payload without
exp, or a verifier that ignores it, produces eternal tokens. Requireexpand reject expired tokens server-side. - Audience and issuer blindness — a token minted for service A replayed to service B works if neither checks
aud/iss.
What JWTs do and do not protect
A JWT’s payload is encoded, not encrypted — anyone holding the token reads every claim, exactly as the decoder shows. Never put passwords, API keys, or personal data in the payload. What the signature does guarantee: the claims were written by whoever holds the key and have not been modified since. Two direct consequences:
- Store JWTs like credentials: HttpOnly cookies beat localStorage for XSS resistance.
- Short-lived access tokens (minutes) with a refresh rotation beat week-long tokens — the
expclaim is only useful if it is short.
A verification checklist that survives review
When your backend accepts a token, it should, in order: verify the signature against a pinned algorithm and trusted key; check exp and nbf against the current time (with a small clock-skew allowance); check iss and aud against expected values; and only then trust the claims. Client-side, decoding is fine for reading exp to decide when to refresh — but the client’s decode is never a substitute for the server’s verification.
Debugging a rejected token? Decode the payload with the JWT Decoder and compare each claim against the server configuration — mismatched iss/aud and clock skew cause the majority of “invalid token” support tickets. And when you need to issue HMAC-signed tokens or verify a webhook signature in development, the HMAC Calculator computes reference values locally.