JSON Web Tokens (JWT)
A JWT is a compact, signed, self-contained token that carries claims about a user. Instead of the server storing session state and handing out an opaque pointer (as with sessions), the server signs a token containing the user's identity and gives it to the client. On each request the client presents the token; the server verifies the signature and trusts the claims inside — no database lookup required. That statelessness is JWT's superpower and its biggest footgun. This page explains the anatomy, the signature, and the security rules before you implement it.
Anatomy: three Base64URL parts
eyJhbGciOiJIUzI1NiJ9 . eyJzdWIiOiI0MiIsInJvbGUiOiJ1c2VyIn0 . 3xH8...sig
└────── HEADER ──────┘ └──────────── PAYLOAD ────────────┘ └─ SIGNATURE ─┘
HEADER { "alg": "HS256", "typ": "JWT" } ← which algorithm
PAYLOAD { "sub": "42", "role": "user", "exp": 1719000000 } ← the claims
SIGNATURE HMAC/RSA over (header + "." + payload) using the secret/keyThe signature is the whole point
Standard claims
Claim | Meaning |
|---|---|
| Subject — who the token is about (user id) |
| Issued At — when it was created |
| Expiration — after this, verification fails |
| Issuer — who created it |
| Audience — who it’s intended for |
| Unique token id — useful for denylists |
The infamous `alg: none` and algorithm confusion
// VERIFY with an explicit allow-list of algorithms — never trust the header's alg:
jwt.verify(token, secret, { algorithms: ['HS256'] }) // ✅ pinned
// ❌ NEVER do this — lets the attacker pick the algorithm:
jwt.verify(token, secret) // alg taken from token headerWhere to store the token client-side
Storage | XSS risk | CSRF risk | Notes |
|---|---|---|---|
| High — JS-readable, stealable | Low | Convenient but XSS = token theft |
| Low — JS can’t read it | Needs | Safer; behaves like a session cookie |
In-memory only | Lower (gone on reload) | Low | Combine with a refresh flow |
JWT vs session — pick deliberately
Stateless — no per-request DB lookup; great for APIs, mobile, and microservices.
Cross-service — any service holding the key (or public key) can verify without a shared session store.
Hard to revoke — a valid token works until
exp; instant logout needs a denylist or short expiry + refresh.Don’t reach for JWT by default for a classic first-party web app — sessions revoke more cleanly.