JWTs explained: what is inside a token and mistakes to avoid
A JSON Web Token (JWT) is a compact string used to carry claims between systems, commonly to represent a signed-in user. It looks like three blocks of random characters separated by dots, but it is more readable than it appears.
The three parts
eyJhbGciOiJIUzI1NiJ9 . eyJzdWIiOiI0MiIsImV4cCI6MTc2MDAwMDAwMH0 . dBjftJeZ4CVP...
header payload signature
- Header: JSON describing the token, such as
{"alg":"HS256","typ":"JWT"}. - Payload: JSON claims, such as
{"sub":"42","exp":1760000000}. - Signature: a cryptographic signature over the first two parts.
The header and payload are Base64URL-encoded, not encrypted. Anyone holding the token can read them, which is exactly what a JWT decoder does.
Common claims
| Claim | Meaning |
|---|---|
iss | Who issued the token |
sub | Who the token is about (usually a user id) |
aud | Who the token is intended for |
exp | Expiry time, in Unix seconds |
nbf | Not valid before this time |
iat | When it was issued |
Times are Unix timestamps in seconds. Use the timestamp converter when you need to read them by eye.
Decoding is not verifying
This is the most important idea. A decoded token shows what it claims. Only checking the signature with the right key proves the claims were not altered and came from the issuer. Never make an access decision based on a token you have only decoded.
Mistakes to avoid
- Putting secrets in the payload. It is readable by anyone. Do not include passwords, personal data you would not want exposed, or internal details.
- Accepting
alg: none. Unsigned tokens must be rejected. Servers should allow a fixed list of algorithms rather than trusting the header. - Not checking
exp,audandiss. A valid signature on an expired token, or one meant for another service, should still be refused. - Algorithm confusion. Treat the algorithm as server configuration, not as something the token gets to choose.
- Long-lived access tokens. A stolen token works until it expires. Use short lifetimes with refresh tokens.
- Storing tokens where scripts can read them.
localStorageis exposed to any cross-site-scripting bug. Consider HttpOnly cookies for browser apps. - Pasting production tokens into random websites. A live token is a credential. Use a decoder that runs locally in your browser, and confirm it with the Network tab test.
Quick debugging checklist
- Decode the token and check
exp: has it expired? - Check
audandiss: do they match your service’s configuration? - Check the
algin the header matches what your verifier expects. - Compare clocks: small clock skew between servers can make a fresh token look “not yet valid”.