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...              ← signature

Decode 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

ClaimNameMeaning
issIssuerWho created the token
subSubjectWho the token is about, usually a user ID
audAudienceWhich service the token is for
expExpirationUnix time after which it must be rejected
nbfNot beforeUnix time before which it must be rejected
iatIssued atUnix time it was created
jtiJWT IDA 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:

  1. Verify the signature with the expected key and an allow-list of algorithms.
  2. Reject the token if exp has passed or nbf has not arrived.
  3. Check iss and aud match 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.

Tools for this guide