JWT validation: do's and don'ts
Updated 2026-05-07
JWT validation is conceptually simple and operationally subtle. The bypasses below are not theoretical, every item has been the source of production CVEs at named vendors. The do's and don'ts here are the floor; high-security flows (FAPI, banking) layer on additional requirements like sender-constrained tokens (DPoP, mTLS).
For broader OAuth 2.1 hardening, see the OAuth/OIDC client config best-practice and the OAuth 2.1 explained guide.
Do
Pin the expected signing algorithm explicitly
Some JWT libraries default to accepting whatever algorithm the token claims (alg header). This permits algorithm-substitution attacks where an RS256 token gets validated as HS256 using the public key as the HMAC secret.
Multiple JWT library CVEs have traced to algorithm-substitution: the 2015 alg=none class, the 2018 RS256/HS256 confusion class. OWASP JWT Cheat Sheet explicitly recommends pinning alg at validation time.
Validate signature, issuer, audience, expiry, and not-before
Each of these can be spoofed independently. A valid signature with wrong issuer means the token came from a different IdP. A valid signature with wrong audience means the token was meant for a different relying party. A valid signature past expiry means the user's session ended.
OIDC specification Section 3.1.3.7 enumerates the validation requirements. Multiple production bypasses have traced to skipped audience or issuer validation.
Use a vetted library with current security patches
JWT validation has subtle timing-attack and parsing-edge-case vulnerabilities that vetted libraries handle correctly. Hand-rolled validation routinely introduces these bugs.
OWASP and the IETF JOSE working group both recommend named libraries (jose for Node, PyJWT for Python, jsonwebtoken for Go, jjwt for Java). Custom JWT parsers have been the source of multiple CVEs across major SaaS vendors.
Cache the IdP's JWKS but honor the kid header for key rotation
JWKS endpoints are public and rate-limited; caching reduces load. The kid (key ID) header lets the IdP rotate keys gracefully, old tokens validate against the old key, new tokens against the new key. Without kid-aware caching, key rotation breaks tokens in flight.
Standard OIDC operational pattern. Documented kid-aware libraries: jose (Node), PyJWT, jjwt. Cache JWKS for 5–10 minutes; refresh on kid miss before failing.
Don't
Don't accept tokens with alg=none
JWTs with alg=none are unsigned and trivially forgeable. The pattern existed for backwards compatibility in early JWT libraries and was the source of authentication bypass in multiple products.
CVE-2015-9235 and many subsequent disclosures. Modern libraries reject alg=none by default; verify your library does.
Don't validate the JWT and skip checking the audience
A valid JWT for service A is not a valid JWT for service B even if both share the same IdP. The audience claim names which service the token was issued for; skipping the check accepts cross-service tokens.
OIDC Section 3.1.3.7 explicitly requires audience validation. Production bypasses at multiple vendors have used cross-audience JWT replay specifically because audience wasn't checked.
Don't fetch JWKS on every request without caching
JWKS fetched per-request creates a hot dependency on the IdP's availability for every authenticated API call. The right pattern caches JWKS for minutes and refreshes on kid miss.
Production outages at major SaaS vendors have traced to IdP JWKS endpoint slowness amplifying through uncached relying parties.
Don't trust JWTs from URLs, query strings, or unauthenticated endpoints
JWTs that travel via URL leak via referrer headers, browser history, and logs. JWTs from unauthenticated endpoints can be forged by anyone who knows the format.
OAuth 2.1 forbids bearer tokens in URLs. Multiple production token leaks have traced to JWT-in-URL patterns, including via browser referrer headers when users navigated from authenticated to external pages.