Glossary
JSON Web Token
A JSON Web Token is a compact, URL-safe way to carry a set of claims — who the subject is, who issued the token, when it expires — in a structure whose integrity is protected by a signature. It is signed, not encrypted: anyone holding the token can read every claim in it by decoding base64. Its defining trade-off is that a signed token can be verified without consulting a database, which is also what makes revoking one before it expires difficult.
The three parts
A header naming the signing algorithm and, optionally, a key identifier.
A payload of claims. Registered claims include `iss` for issuer, `sub` for subject, `aud` for audience, `exp` for expiry and `iat` for issued-at; anything else may be added.
A signature over the first two parts, produced with a shared secret or a private key.
The three are base64url-encoded and joined with dots. Base64 is an encoding, not encryption — a token pasted into any decoder shows its contents.
The validation mistakes that matter
Accepting the `alg` value from the token itself. A token claiming `none` must be rejected, and a token claiming a symmetric algorithm must not be verified against a public key. Both have been exploited in real libraries.
Not checking `exp`, which turns a short-lived token into a permanent one.
Not checking `aud`, so a token issued for one service is accepted by another.
Not checking `iss`, so a token from a different issuer is accepted.
The correct pattern is to fix the expected algorithm, issuer and audience in the verifying code rather than reading them from the token.
Why revocation is hard
The point of a signed token is that it can be verified without a lookup. That property is exactly what makes it impossible to invalidate without one.
Logging out, disabling an account or changing a password does not stop an already-issued token working until it expires.
The usual mitigation is short lifetimes — minutes — with a separate long-lived refresh token that is stored and can be revoked.
A denylist of revoked token identifiers works and reintroduces the database lookup the design was avoiding, which is a reasonable trade for high-consequence systems.
Where to put one
An `Authorization: Bearer` header is the conventional place for an API, and it is not sent automatically by the browser, which sidesteps cross-site request forgery.
Local storage is readable by any script on the page, including an injected one, which makes it a poor place for a token that grants access.
An `HttpOnly`, `Secure`, `SameSite` cookie is not readable by scripts and is sent automatically, which reintroduces the forgery concern and is handled by the `SameSite` attribute.
Never put anything in a token you would not publish: it is readable by whoever holds it.
Frequently asked questions
- Is a JWT encrypted?
- No. It is signed. The claims are base64-encoded and readable by anyone holding the token. A separate specification exists for encrypted tokens.
- How do I revoke a JWT?
- You generally cannot before it expires. The usual approach is short lifetimes with a revocable refresh token, or a denylist that reintroduces a lookup.
- Should a token go in local storage or a cookie?
- Local storage is readable by any script including an injected one. An HttpOnly, Secure, SameSite cookie is not, at the cost of needing forgery protection.
Sources
Related
VeriFixScan crawls a site and applies its checks to every page it reaches, keeping the evidence behind each finding. Scanning one website is free.
Scan a website