JWT Explained
A JSON Web Token is a compact, signed piece of JSON that a server gives a client to prove who it is. It has three Base64URL parts joined by dots: a header naming the algorithm, a payload of claims, and a signature. Anyone can read the first two; only the signature, checked with the right key, proves the token is genuine and unchanged.
The three parts
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 ← header
.eyJzdWIiOiI0MiIsImV4cCI6MTc2MjEzMTYwMH0 ← payload
.l0Hk7rxqz0s1bHZ3n0p6m9... ← signatureDecode the header and you get {"alg":"HS256","typ":"JWT"}. Decode the payload and you get the claims, such as {"sub":"42","exp":1762131600}. The signature is the algorithm applied to header.payload with a key. Paste any token into the JWT decoder to see this for yourself.
Registered claims
| Claim | Name | Meaning |
|---|---|---|
iss | Issuer | Who created the token |
sub | Subject | Who the token is about, usually a user ID |
aud | Audience | Which service the token is for |
exp | Expiration | Unix time after which it must be rejected |
nbf | Not before | Unix time before which it must be rejected |
iat | Issued at | Unix time it was created |
jti | JWT ID | A unique ID, useful for revocation lists |
Decoding is not verifying
The header and payload are only encoded, not encrypted. Any client, proxy or log file that sees the token can read them. What stops someone changing "role":"user" to "role":"admin" is the signature: change one byte and it no longer matches. So your server must, on every request:
- Verify the signature with the expected key and an allow-list of algorithms.
- Reject the token if
exphas passed ornbfhas not arrived. - Check
issandaudmatch what you expect.
Mistakes to avoid
- Accepting
alg: none. An unsigned token must never be trusted. Pin the algorithm on the server instead of reading it from the header. - Algorithm confusion. If a server expects RS256 but accepts HS256, an attacker can sign a token with the public key as the HMAC secret. Libraries fixed this years ago, but only if you pass the expected algorithm.
- Secrets in the payload. Never put passwords, card numbers or personal data in a JWT.
- Long-lived access tokens. A stolen token works until it expires. Keep access tokens short and use refresh tokens.
- Weak HMAC secrets. An HS256 secret should be at least 256 random bits, not a word or a phrase, or it can be cracked offline.
Frequently asked questions
Where should I store a JWT in the browser?
For web apps, the safest default is an HttpOnly, Secure, SameSite cookie, which JavaScript cannot read, so an XSS bug cannot steal it. localStorage is readable by any script on the page. If you use cookies, protect state-changing requests against CSRF.
How do I log a user out if JWTs cannot be revoked?
Keep access tokens short-lived (5 to 15 minutes) and pair them with a refresh token stored on the server, which you can delete. For immediate revocation, keep a deny-list of token IDs (the jti claim) until they expire.
HS256 or RS256?
HS256 uses one shared secret to sign and verify, so every service that verifies tokens can also create them. RS256 or ES256 sign with a private key and verify with a public key, so only the issuer can create tokens. Use asymmetric keys when more than one service verifies tokens.