JWTs (JSON Web Tokens) are compact, URL-safe tokens that encode claims between two parties. They power access tokens, API keys, and identity assertions across the industry — and they are responsible for an outsized share of authentication vulnerabilities when implemented carelessly. Getting JWTs right in 2026 comes down to a handful of non-negotiable rules.
What changed in 2026
- Algorithm confusion attacks are well-documented. The "alg:none" attack and HS256/RS256 confusion bugs have been in security curricula for years, yet they keep appearing in CVEs. Use a maintained library; don't parse JWTs manually.
- Short-lived tokens became the norm. With refresh token rotation standardized in OAuth 2.1, there is no good reason for access tokens longer than 15–30 minutes.
- JWK (JSON Web Key) rotation tooling matured. Rotating signing keys without downtime is now a first-class operation in all major identity platforms.
- Token binding via DPoP gained traction. DPoP (Demonstrating Proof of Possession) ties a JWT to a specific key pair, making stolen tokens useless without the matching private key.
JWT anatomy
A JWT is three base64url-encoded JSON objects joined by dots: header.payload.signature.
// Header
{ "alg": "RS256", "typ": "JWT", "kid": "key-2026-01" }
// Payload (claims)
{
"sub": "user_abc123",
"iss": "https://auth.myapp.com",
"aud": "https://api.myapp.com",
"exp": 1748800000,
"iat": 1748799100,
"scope": "read:orders write:cart"
}
// Signature: RS256(base64url(header) + "." + base64url(payload), privateKey)
The payload is not encrypted — it is base64url-encoded and readable by anyone who has the token. Encryption requires JWE (JSON Web Encryption), which is a different, heavier standard.
Algorithm comparison
| Algorithm |
Type |
Use case |
Notes |
| HS256 |
Symmetric (HMAC) |
Single-service, self-contained |
Same secret signs and verifies — don't share it |
| RS256 |
Asymmetric (RSA) |
Public API, multi-service |
Private key signs; public key verifies; safe to publish JWKS |
| ES256 |
Asymmetric (ECDSA) |
Same as RS256 |
Smaller tokens and signatures than RS256 |
| PS256 |
Asymmetric (RSA-PSS) |
High-security requirements |
Preferred over RS256 where FIPS compliance matters |
| none |
None |
Never |
Always reject; strip algorithm confusion attacks |
Default choice: RS256 or ES256 for any system with multiple services or external clients. HS256 only when a single service both issues and consumes tokens and the secret is kept out of reach.
The mandatory validation checklist
// Using jose (Node.js) — verification example
import { jwtVerify, createRemoteJWKSet } from 'jose';
const JWKS = createRemoteJWKSet(new URL('https://auth.myapp.com/.well-known/jwks.json'));
async function verifyToken(token) {
const { payload } = await jwtVerify(token, JWKS, {
issuer: 'https://auth.myapp.com', // iss claim must match
audience: 'https://api.myapp.com', // aud claim must match
algorithms: ['RS256'], // explicit allowlist — no "none"
clockTolerance: '30s', // allow 30s clock skew
});
// exp is checked automatically by jwtVerify
return payload;
}
Never skip iss, aud, or exp validation. The algorithms allowlist prevents algorithm confusion attacks.
How to pick
- Single service issuing and consuming? HS256 with a secret from your secrets manager (not hardcoded). Rotate the secret on a schedule.
- Multiple services or external API? RS256 or ES256 with a JWKS endpoint. Services pull the public key; only the auth server holds the private key.
- Need the payload encrypted? Use JWE, or encrypt specific sensitive claims before putting them in the JWT, or keep sensitive data server-side and put only a reference ID in the token.
- Building a long-lived API key? Consider opaque tokens with a database lookup instead — they can be revoked instantly without the revocation infrastructure JWTs require.
- Need token binding? Implement DPoP if your auth server supports it (Auth0, Keycloak, Microsoft Entra all do in 2026).
Common mistakes
Trusting alg: none. Some older libraries accepted unsigned tokens if the header specified alg: none. Always use an allowlist of acceptable algorithms and reject tokens outside it.
Not checking aud. An access token for your API can be replayed against another service's API if neither validates the audience claim. Always set and check aud.
Storing in localStorage. XSS can read localStorage. Store access tokens in memory (for SPAs) or use HTTP-only cookies with a backend-for-frontend. See OAuth explained in 2026 for the BFF pattern.
Long expiry with no revocation plan. A JWT can't be "cancelled" without a blocklist or a very short expiry. If your tokens live for 24 hours, a compromised token is exploitable for up to 24 hours. Use 15-minute tokens with refresh token rotation.
Signing key leakage. A leaked HS256 secret or RS256 private key means an attacker can forge any token for any user. Use a secrets manager (Vault, AWS Secrets Manager), not environment variables in your source code.
What to skip
- Rolling your own JWT parser. The JWT specification has enough sharp corners (algorithm confusion, padding, base64url vs base64) that every custom implementation has bugs. Use
jose, python-jose, or java-jwt.
- Storing sensitive PII in claims. The payload is readable by the token holder and anyone it passes through. Put a user ID in
sub; leave email, address, and payment data server-side.
- Using JWTs as CSRF tokens. They are stateless and don't have the properties needed for CSRF mitigation. Use a proper CSRF token (synchronizer pattern or double-submit cookie).
FAQ
Can I revoke a JWT?
Not directly — that's the stateless trade-off. Options: short expiry (15 min), a token revocation list (adds a database lookup, partially defeats the stateless benefit), or refresh token rotation (the refresh token is revocable; short access tokens limit the damage window).
What is the difference between a JWT access token and a session cookie?
A session cookie is an opaque reference; the server looks up the session state on every request. A JWT is self-contained; the server validates the signature locally with no lookup. JWTs scale better; sessions revoke instantly.
Should my JWT contain the user's role?
Claims like role or scope are common and fine. Avoid putting data that changes frequently (e.g., account balance) in the token — it will be stale. Verify role claims server-side against your policy engine for sensitive operations.
How do I rotate signing keys without downtime?
Publish multiple keys in your JWKS endpoint. Issue new tokens with the new kid; old tokens with the old kid continue to validate until they expire. Remove the old key from JWKS after tokens signed with it have all expired.
Where to go next
OAuth explained in 2026, Content security policy in 2026, and API rate limiting in 2026.