JWT anatomy: decode vs. verify
The three segments of a JSON Web Token, what decoding tells you and what it does not, and the claims that matter when debugging auth.
Three segments, two dots
A JWT is header.payload.signature — three Base64URL-encoded segments joined by dots. The header declares the signing algorithm (alg) and token type. The payload carries the claims: the actual data. The signature is a cryptographic proof, computed over the first two segments, that the token was issued by someone holding the key and has not been altered.
All three segments are only encoded, not encrypted. Anyone who sees the token can read the payload — which is why you never put passwords, secrets, or personal data in JWT claims.
Decoding is not verifying
Decoding a JWT — what a debugger does — just Base64-decodes the segments and pretty-prints the JSON. It answers 'what does this token claim?' It does not answer 'is this token genuine?' Verification requires the signing key (or the public key, for asymmetric algorithms like RS256/EdDSA) and recomputing the signature.
This distinction is the most important security fact about JWTs. Client-side decoding is safe and useful for reading expiry or roles; trusting decoded claims server-side without verifying the signature is the bug class behind countless auth bypasses.
The claims you check when debugging
- iss — issuer: who minted the token. Wrong issuer means you are looking at a token from a different environment.
- sub — subject: the user or principal the token represents.
- aud — audience: which service the token is meant for. An aud mismatch is a classic 401 cause.
- exp — expiration, as a Unix timestamp in seconds. Convert it and compare with your clock.
- iat / nbf — issued-at and not-before. A token from the future (clock skew) fails validation just like an expired one.
- scope / roles — authorization detail, entirely application-defined.
Common failure modes
Expired tokens (exp in the past) are the everyday case — check exp against the actual current time, not your assumption of it. Clock skew between issuer and validator breaks nbf and iat checks; most libraries allow a small leeway for this. Algorithm confusion — where a server accepts alg=none or trusts the header's algorithm choice instead of enforcing one — is a real attack, not just a bug: modern libraries forbid it, but custom verification code sometimes does not.
Finally: a JWT is valid until it expires. There is no built-in revocation. Short exp times plus refresh tokens are the standard mitigation; a denylist is the escape hatch.
References
Related tools
- JWT DecoderDecode JWT headers and payloads locally — no signature verification.
- Base64 Encoder / DecoderEncode and decode Base64 and Base64URL locally in your browser.
- Unix Timestamp ConverterConvert Unix timestamps to readable dates and back.