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.
High because algorithm confusion or missing signature validation lets an attacker mint valid tokens for any user without ever obtaining the secret key.
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.
ID: security-hardening.auth-session.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 algorithms option explicitly set (not left as default accept-all). Verify that exp (expiry) is checked. Check whether aud (audience) and iss (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, or alg: "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 jose library (Web Crypto compatible) or jsonwebtoken with 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',
})