JWT Security Best Practices for 2026 (With Real‑World Examples)
March 18, 2026 · JWT, Security, APIs
JSON Web Tokens (JWTs) are everywhere: single‑page apps, mobile APIs, service‑to‑service auth, and identity federation. They’re also easy to misuse. The most common JWT incidents in 2025–2026 come from misconfigured validation, weak key management, and unsafe storage in browsers. This guide summarizes the practices that actually prevent breaches in production, with concrete code and checks you can copy.
Scope note: this article covers signed JWTs (JWS), not encrypted JWTs (JWE). Most APIs should use signed tokens and protect the transport layer with TLS 1.2+.
1) Always validate signature and critical claims
JWT validation is not just signature verification. Your verifier must also check the claim set. Required checks in 2026:
- iss (issuer): exact match for your IdP or auth service.
- aud (audience): must include your API or client ID.
- exp (expiration): reject expired tokens. Use tight clock skew (30–60s max).
- nbf (not before): reject tokens not yet valid.
- sub (subject): required if you need a user identity.
- jti (JWT ID): recommended for replay detection and revocation lists.
If any of these claims are missing, consider the token invalid unless you explicitly documented that omission.
Node.js (jsonwebtoken) validation example
import jwt from "jsonwebtoken";
const JWKS_PUBLIC_KEY = process.env.JWT_PUBLIC_KEY; // PEM or fetched key
export function verifyToken(token) {
return jwt.verify(token, JWKS_PUBLIC_KEY, {
algorithms: ["RS256"],
issuer: "https://auth.example.com/",
audience: "https://api.example.com/",
clockTolerance: 30, // seconds
maxAge: "10m" // extra guardrail even if exp is present
});
}
Python (PyJWT) validation example
import jwt
public_key = open("/etc/keys/jwt-public.pem").read()
payload = jwt.decode(
token,
public_key,
algorithms=["RS256"],
issuer="https://auth.example.com/",
audience="https://api.example.com/",
options={"require": ["exp", "iss", "aud", "sub"]},
leeway=30
)
2) Use modern algorithms and forbid “none”
Never accept alg=none. That misconfiguration still appears in legacy libraries and custom verification code. Stick to:
- RS256 (RSA SHA‑256) for most external identity providers.
- ES256 (ECDSA SHA‑256) for smaller keys and faster verification.
- EdDSA (Ed25519) if your library and IdP fully support it.
Avoid HS256 for tokens issued by third parties or multi‑tenant systems, because shared secrets enable privilege confusion. If you use HS256 internally, keep the secret in a dedicated key vault and rotate regularly.
3) Separate access tokens and refresh tokens
Access tokens should be short‑lived (5–15 minutes). Refresh tokens should be longer‑lived (7–30 days) but stored and protected differently. Best practice in 2026:
- Access token in memory or an HttpOnly, Secure, SameSite=Lax/Strict cookie.
- Refresh token in an HttpOnly, Secure cookie only.
- Rotate refresh tokens on every use (refresh token rotation).
- Store refresh token hashes server‑side to invalidate on logout or compromise.
Never put refresh tokens in localStorage. XSS still happens, and localStorage is the first place attackers look.
4) Use a narrow, explicit JWT payload
JWTs should be minimal. Include only what your API needs on every request. Avoid putting PII or role data that changes frequently. A lean payload reduces risk and improves cacheability.
Example: a minimal access token payload:
{
"iss": "https://auth.example.com/",
"sub": "user_7f3b91",
"aud": "https://api.example.com/",
"exp": 1763120400,
"iat": 1763119800,
"jti": "0d11c1a1-0d23-4b2e-a05b-9a2a6b8219a7",
"scp": ["read:profile", "read:projects"]
}
Use a UUID generator to create high‑entropy jti values.
5) Prefer scopes over roles in tokens
Roles are coarse and long‑lived. Scopes are fine‑grained and safer for APIs. The best pattern is:
- Token contains scopes (permissions).
- Server maps scopes to internal authorization rules.
- Roles are stored server‑side, not inside JWTs.
This avoids stale roles in a token that lasts 15 minutes or more.
6) Implement key rotation with JWKS
Static keys are a liability. Use JWKS (JSON Web Key Set) for key rotation and caching. Your API should:
- Fetch the JWKS from your IdP.
- Cache keys for 5–15 minutes.
- Support multiple active keys (kid matching).
- Fail closed if the key cannot be found for a given
kid.
For quick inspection of JWT headers or JWKS payloads, a JSON formatter is useful during debugging (never in production logs).
7) Protect against algorithm confusion
Algorithm confusion happens when a verifier accepts the wrong algorithm for a given key type. Example: using an RSA public key to validate an HS256 signature. To prevent this:
- Explicitly set the acceptable algorithms list.
- Use the right key type for the algorithm.
- Reject any token where the header
algdoesn’t match your expected algorithms.
8) Use strict Base64URL handling
JWTs are Base64URL‑encoded. Don’t use lenient decoders that accept invalid characters or padding unless your library requires it. Use a dedicated decoder if you are inspecting tokens, and remember: decoding is not validation. A Base64 decoder can help you read the header and payload while troubleshooting.
9) Prevent replay and token theft
JWTs are bearer tokens: whoever holds it can use it. In 2026, the strongest defenses are layered:
- Short access token TTL (5–15 minutes).
- Token binding where possible (mTLS or DPoP for high‑risk APIs).
- Refresh token rotation with reuse detection.
- Logout revocation list keyed by
jtior session ID.
If you store JWTs in cookies, use SameSite=Strict or Lax and CSRF tokens for state‑changing requests.
10) Limit token lifetime and add server‑side checks
Even if JWTs are self‑contained, you can still do server‑side checks:
- Maintain a token revocation list for stolen or logged‑out sessions.
- Store a session version in your user record and compare to a claim like
sv. - Invalidate tokens after password resets or MFA changes.
A common pattern is to include sv in the token and compare it to the user’s current session version in your database. Increment sv on critical security events.
11) Don’t log raw JWTs in production
JWTs often contain user identifiers and sensitive scopes. Logging them can create a replay risk. Instead:
- Log only the
jtior a hash of the token. - Redact Authorization headers from request logs.
- Use structured logging with redaction policies.
12) Use HTTPS everywhere, no exceptions
JWTs must only travel over TLS. Any HTTP endpoint is an immediate compromise risk. Enforce HTTPS with HSTS and reject insecure requests at the load balancer.
13) Sanitize and validate input claims
If you accept custom claims (e.g., tenant_id), validate format and length. A regex tester is handy for building a strict pattern for claims like tenant IDs, region codes, or API keys. Example pattern for a tenant slug:
^[a-z0-9]{3,32}$
14) Keep JWT sizes small
Large JWTs slow down every request and can exceed header limits (often 8–16 KB depending on proxy). Keep payloads under 2 KB when possible. Avoid embedding large arrays or profile objects. Store those server‑side.
15) Document token format and version it
JWTs are a contract. Add a ver claim and document your schema. When you change formats, increment the version and support old formats during a transition period. This is essential for multi‑client environments.
Practical checklist (copy/paste)
- Access tokens: 5–15 minutes, refresh tokens: 7–30 days.
- Validate
iss,aud,exp,nbf,sub. - Explicit algorithms list; reject
none. - Use RS256/ES256; avoid HS256 for multi‑tenant or third‑party tokens.
- Implement JWKS key rotation with caching.
- Store tokens in HttpOnly cookies or memory, not localStorage.
- Rotate refresh tokens and detect reuse.
- Do not log raw tokens in production.
How DevToolKit helps during debugging
When validating JWTs locally or during incident response, simple tooling saves time:
- JSON Formatter — inspect decoded header/payload safely.
- Base64 Encoder/Decoder — decode JWT segments (Base64URL).
- Regex Tester — build patterns for custom claim validation.
- UUID Generator — create strong
jtivalues. - URL Encoder/Decoder — troubleshoot OAuth/JWT transport issues in query strings.
FAQ
Q1: Are JWTs secure by default?
A1: JWTs are secure only when you validate signatures and claims correctly, use modern algorithms, and keep token lifetimes short.
Q2: How long should an access token live in 2026?
A2: Access tokens should live 5–15 minutes for most web and mobile APIs, with refresh tokens handling longer sessions.
Q3: Should I store JWTs in localStorage?
A3: No, you should store JWTs in HttpOnly Secure cookies or in memory to reduce XSS theft risk.
Q4: Is HS256 still acceptable?
A4: HS256 is acceptable only for internal, single‑tenant systems with well‑protected secrets; otherwise prefer RS256 or ES256.
Q5: Do I need a JWT revocation list?
A5: Yes, you should maintain a revocation mechanism for logout, password resets, and compromised tokens.
Recommended Tools & Resources
Level up your workflow with these developer tools:
Try DigitalOcean → Try Neon Postgres → Designing Data-Intensive Applications →Dev Tools Digest
Get weekly developer tools, tips, and tutorials. Join our developer newsletter.