What is a JWT?
On this page
A JWT (JSON Web Token, pronounced “jot”) is a signed bundle of JSON used for authentication and authorization. The signature lets the receiver verify the contents weren’t tampered with, without calling back to the issuer to check.
That last property — stateless verification — is what makes JWTs popular and also what makes them dangerous when misused. This page covers both.
A complete JWT
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIEV4YW1wbGUiLCJpYXQiOjE1MTYyMzkwMjJ9.
6CjSLjOygL01Z5yY2LkZTtkrfZRhzmnGGc5n7QqJEQg
Three parts separated by dots:
- Header (base64url-encoded JSON) — what algorithm signed it
- Payload (base64url-encoded JSON) — claims about the user / session
- Signature — proof the first two haven’t been modified
You can paste any JWT into the decoder and see all three parts. Decoding is just base64 + JSON.parse — no secret needed.
What’s in each part
Header
{
"alg": "HS256",
"typ": "JWT"
}
Tells the receiver which algorithm to expect. HS256 is HMAC-SHA256.
Other common algorithms: RS256 (RSA-SHA256), ES256 (ECDSA-SHA256),
EdDSA.
Payload
{
"sub": "user-12345",
"name": "Alice Example",
"iat": 1516239022
}
Contains “claims” — assertions about the subject. The JWT spec defines
seven registered claims (iss, sub, aud,
exp, nbf, iat, jti), but you can include any JSON.
Signature
The hard part. For HS256:
HMAC-SHA256(
base64url(header) + "." + base64url(payload),
secret
)
Then base64url-encode the result. The receiver re-computes this with the same secret; if it matches, the JWT is authentic. If it doesn’t, either the token was modified or the secret is wrong.
What JWTs are NOT
Three confusions worth clearing up:
- Not encrypted. The header and payload are plain base64 — anyone can read them. JWE (JSON Web Encryption) is the encrypted variant, but it’s rarely used. If your JWT contains a credit card number, you have a problem.
- Not opaque tokens. Anyone can decode a JWT without the secret. Useful for debugging, dangerous if you assume otherwise.
- Not the same as session tokens. Session tokens are usually random IDs that the server looks up in a database. JWTs are self-contained — the server reads the claims directly from the token.
Where JWTs fit well
- Stateless API authentication. A JWT lets a server validate the caller’s identity without a database lookup, which scales horizontally better than session tokens.
- Cross-service auth. Service A signs a token; service B verifies it with the public key. No shared session store needed.
- OIDC ID tokens. OpenID Connect uses JWTs for the ID token that conveys identity claims (email, name, sub).
- Short-lived access tokens. OAuth 2.0 access tokens are often JWTs.
- Single sign-on. SAML’s JSON-era equivalent.
Where JWTs fit poorly
- Long-lived sessions. JWTs can’t be revoked easily — a stolen token is valid until it expires. Long expiry windows + no revocation = real risk. For session-style usage, prefer opaque random tokens with a server-side session store.
- Tokens carrying sensitive data. Anyone who reads the JWT reads the data. Don’t put SSNs, credit cards, or private notes in claims.
- Single-tenant systems with no scaling concern. Sessions are simpler and easier to revoke. JWT’s main benefit is statelessness.
- Use cases where you’ll need to invalidate tokens. Logout, password
change, security incident — sessions handle these naturally; JWTs
require workarounds (short expiry + refresh tokens, blocklist by
jti, key rotation).
See JWT vs session cookies for the full comparison.
Common security mistakes
1. Trusting alg without pinning
Old JWT libraries had a vulnerability: the receiver would read the
algorithm from the JWT itself, then verify with a key matched to that
algorithm. An attacker could forge a token with alg: HS256 and the
public key as the HMAC secret — the server accepted it.
Fix: always specify the expected algorithm explicitly:
jwt.verify(token, secret, { algorithms: ["HS256"] });
2. Accepting alg: none
The JWT spec defines an none algorithm that means “no signature.”
Some old libraries treated this as “signature passed.” Catastrophic.
Fix: modern libraries reject none by default. Verify your library
does, or pin algorithms explicitly.
3. Weak HMAC secrets
A 6-character secret can be brute-forced offline. HS256 needs at least 256 bits of entropy.
Fix: generate secrets with a CSPRNG:
import { randomBytes } from "crypto";
const secret = randomBytes(32).toString("base64");
4. Storing JWTs in localStorage
localStorage is accessible to any JavaScript on the page, including
malicious script injected via XSS. A stolen JWT is a stolen session.
Fix: store in HttpOnly cookies (inaccessible to JS) or use shorter expiry + secure refresh-token flow.
5. Long expiration with no revocation strategy
A 30-day JWT with no revocation means a stolen token gives 30 days of access. Even after password change.
Fix: short access token (15 min), long refresh token (days/weeks),
revocation list keyed by user or jti.
6. Putting too much in the payload
JWTs travel in headers. Some HTTP infrastructure caps headers at 8KB. A bloated JWT can break requests in production.
Fix: include only what’s needed for authorization decisions; look up the rest from the database.
How big is a JWT?
A typical signed JWT is 300–800 bytes. With many claims and a long audience list it can grow past 1 KB. Beyond ~4 KB you start to risk hitting infrastructure limits.
Algorithms compared
| Algorithm | Type | Speed | Key | Use case |
|---|---|---|---|---|
HS256 | HMAC | Fastest | Shared secret | Internal APIs where both sides share a key |
HS384 / HS512 | HMAC | Slightly slower | Shared secret | Higher-security HMAC |
RS256 | RSA-SHA256 | Slow | Public/private keypair | Public verification (server signs, many can verify) |
RS384 / RS512 | RSA | Slower | Public/private keypair | Higher-security RSA |
ES256 | ECDSA-SHA256 | Faster than RSA | EC keypair | Modern public-key auth |
EdDSA (Ed25519) | EdDSA | Fast | EC keypair | Newest, recommended for new systems |
For symmetric setups (your service signs and your service verifies), HS256 is fine. For “anyone can verify with the public key,” use RS256 or ES256.
Try the tools
- JWT decoder — paste any JWT, see decoded contents with claim explanations
- Verify HMAC — verify HS256/384/512 signatures
- Sign HS256 — generate test tokens
- Standard claims — every registered claim explained
- JWT vs sessions — when to use each
Further reading
- RFC 7519: JSON Web Token — the spec
- RFC 7515: JSON Web Signature — signing details
- jwt.io introduction — the canonical primer