A JSON Web Token looks like line noise: three runs of letters and digits separated by dots. It is actually one of the most readable formats in web development, and that readability is both its convenience and its most misunderstood property. Here is how to read one, and where reading stops being trusting.
The anatomy
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJzdWIiOiJ1c3JfODIifQ . sig...
header payload signatureEach of the first two parts is a JSON object encoded with base64url, the URL-safe Base64 variant. That is encoding, not encryption: anyone who holds the token can decode it in a millisecond. The header says how the token is signed, typically an alg like HS256 or RS256 and a typ of JWT. The payload carries the claims. The signature is raw bytes computed over the first two parts with the issuer's key.
Reading the claims
Claims are just JSON fields, and seven of them are registered with standard meanings: iss says who issued the token, sub whom it is about, aud which service it is for, and jti gives it a unique ID. The three time claims cause the most confusion because they are Unix timestamps, seconds since January 1, 1970: iat is when the token was issued, nbf is the moment it becomes valid, and exp is the moment it stops being accepted. A timestamp like 1751500000 tells a human nothing, which is why a decoder that converts them to local time and says "expires in two hours" saves real debugging time.
The debugging checklist
- 401 with a fresh-looking token: decode it and check
expfirst. Expiry windows are shorter than people expect, often minutes. - Token rejected the instant it is issued: compare
nbfandiatto your server clock. A few seconds of clock skew between machines is enough; that is why validators accept a small leeway. - Valid token, wrong service: check
aud. Tokens issued for one API are routinely replayed against another and correctly refused. - Roles or flags missing: look at what the payload actually contains rather than what the login flow was supposed to put there.
Where trust has to stop
Everything you just read, an attacker can also read, and more importantly, write. A forged token can claim any sub and any role; what stops it is signature verification, which requires the issuer's secret or public key and belongs on the server, in a maintained JWT library. Two rules follow directly. First, never put secrets in a payload, because a JWT is readable by design; confidentiality requires the encrypted JWE variant instead. Second, never accept a token whose header says "alg": "none", a historical footgun that declares the token unsigned.
Treat the token itself like a password while you inspect it: a decoder that runs entirely in the browser exists precisely so that session tokens never land in some website's server logs.