JWT validation covers signature, algorithm, expiry, and audience
Why it matters
JWT algorithm confusion is one of the most exploited vulnerabilities in token-based auth. CWE-347 (Improper Verification of Cryptographic Signature) and CWE-290 (Authentication Bypass by Spoofing) describe how accepting alg: 'none' or failing to whitelist algorithms lets an attacker forge arbitrary tokens without the signing key. OWASP A07 (Identification & Authentication Failures) and NIST 800-53 IA-8 require integrity verification of authentication tokens. An RS256 public key misused as an HS256 secret is a textbook attack that compromises every user account on the system. Missing audience validation allows tokens issued for one service to be replayed against another.
Severity rationale
High because algorithm confusion or missing signature validation lets an attacker mint valid tokens for any user without ever obtaining the secret key.
Remediation
Use the jose library and always specify the exact allowed algorithm. Never allow 'none':
import { jwtVerify, SignJWT } from 'jose'
const secret = new TextEncoder().encode(process.env.JWT_SECRET)
// Signing
const token = await new SignJWT({ userId: user.id })
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('15m')
.setIssuer('https://yourapp.com')
.setAudience('https://yourapp.com/api')
.sign(secret)
// Verification — rejects wrong alg, expired tokens, wrong iss/aud
const { payload } = await jwtVerify(token, secret, {
algorithms: ['HS256'],
issuer: 'https://yourapp.com',
audience: 'https://yourapp.com/api',
})
Never pass an untrusted alg claim from the token header to your verification function.
Detection
-
ID:
jwt-validation -
Severity:
high -
What to look for: List all JWT verification locations in the codebase. For each, examine JWT verification code. Look for the
algorithmsoption explicitly set (not left as default accept-all). Verify thatexp(expiry) is checked. Check whetheraud(audience) andiss(issuer) are validated when present in the token. The most critical failure is accepting algorithm"none"or accepting any algorithm without restriction. -
Pass criteria: JWT verification explicitly specifies allowed algorithms (never
none). Expiry is verified. If audience/issuer claims are present in issued tokens, they are validated on receipt — at least 4 validation fields (signature, algorithm, expiry, audience) must be checked at every verification point. Report: "X JWT verification points found, all Y validate signature, algorithm, expiry, and audience." -
Fail criteria: JWT verification accepts algorithm
"none", or no algorithm whitelist is specified, or expiry is not checked, oralg: "none"could be injected via the token header. -
Skip (N/A) when: The application does not use JWTs (uses session cookies or managed auth provider tokens).
-
Detail on fail: Quote the actual JWT verification code showing the missing validation. Example:
"JWT verification does not specify algorithms array — vulnerable to algorithm confusion attack (HS256/RS256 swap)"or"JWT expiry (exp claim) not verified — tokens remain valid indefinitely after their stated expiry" -
Remediation: Use the
joselibrary (Web Crypto compatible) orjsonwebtokenwith explicit algorithm configuration:import { jwtVerify, SignJWT } from 'jose' const JWT_SECRET = new TextEncoder().encode(process.env.JWT_SECRET) // Signing const token = await new SignJWT({ userId: user.id, role: user.role }) .setProtectedHeader({ alg: 'HS256' }) .setIssuedAt() .setExpirationTime('15m') .setIssuer('https://yourapp.com') .setAudience('https://yourapp.com/api') .sign(JWT_SECRET) // Verification — will reject wrong algorithm, expired tokens, wrong iss/aud const { payload } = await jwtVerify(token, JWT_SECRET, { algorithms: ['HS256'], // Only allow HS256, never 'none' issuer: 'https://yourapp.com', audience: 'https://yourapp.com/api', })
External references
- cwe · CWE-347 — Improper Verification of Cryptographic Signature
- cwe · CWE-290 — Authentication Bypass by Spoofing
- owasp:2021 · A07 — Identification and Authentication Failures
- nist:rev5 · IA-8 — Identification and Authentication (Non-Organizational Users)
Taxons
History
- 2026-04-18·v1.0.0·Initial import from security-hardening·automated