JWT Vulnerabilities
debt(d5/e3/b3/t7)
Closest to 'specialist tool catches it' (d5). The detection_hints list semgrep and psalm as tools, both specialist static analysis tools. The code patterns (accepting alg from token header, no algorithm whitelist, base64_decode without signature verification) are detectable by these tools but not by a standard compiler or default linter pass. Some misuses (e.g. weak secrets, unvalidated claims) may slip through even these tools, but the primary vulnerabilities are in scope for SAST.
Closest to 'simple parameterised fix' (e3). The quick_fix specifies explicitly naming the allowed algorithm when decoding and using firebase/php-jwt — a targeted library swap or configuration change within the authentication layer. Addressing all common_mistakes (algorithm whitelist, secret strength, claim validation) requires touching the JWT verification code in one component, but it does not span multiple files or require architectural rework.
Closest to 'localised tax' (b3). The applies_to contexts are web and api, and JWT verification logic is typically concentrated in authentication middleware or a single service. While authentication is important, the burden is localised — it does not impose a persistent tax across unrelated work streams or define the system's shape.
Closest to 'serious trap' (t7). The misconception field explicitly states the canonical wrong belief: a valid JWT signature is taken to mean the token is fully trustworthy. This contradicts how many developers reason about cryptographic signing — they assume signature validity implies claim validity. The alg confusion attack (accepting whatever algorithm the token declares) is a well-documented gotcha that contradicts intuition built from other authentication systems, placing this at t7.
Also Known As
TL;DR
Explanation
Common JWT vulnerabilities include: accepting the 'none' algorithm (no signature required), algorithm confusion attacks (RS256 public key used as HS256 secret), weak secrets susceptible to offline brute-force, missing expiry validation, and insecure storage of tokens in localStorage. Always validate the algorithm explicitly, reject 'none', use strong secrets or asymmetric keys, verify exp/nbf claims, and store tokens in HttpOnly cookies rather than JavaScript-accessible storage.
How It's Exploited
Diagram
flowchart TD
subgraph alg none attack
J1[Header: alg=none] --> J2[Signature stripped]
J2 --> J3[Server accepts<br/>unsigned token!]
end
subgraph Algorithm confusion
J4[RS256 token] --> J5[Attacker changes<br/>alg=HS256]
J5 --> J6[Signs with<br/>public key as HMAC secret]
J6 --> J7[Server verifies<br/>with same public key]
end
subgraph Fix
F1[Whitelist allowed algorithms<br/>never accept none<br/>validate exp, iss, aud]
end
style J3 fill:#f85149,color:#fff
style J7 fill:#f85149,color:#fff
style F1 fill:#238636,color:#fff
Watch Out
Common Misconception
Why It Matters
Common Mistakes
- Not explicitly specifying the allowed algorithm when verifying — accepting whatever alg the token declares.
- Using a short or guessable HMAC secret — HS256 with a weak secret is crackable offline.
- Not validating exp, nbf, and iss claims after signature verification.
- Storing sensitive data in the payload without encrypting it — the payload is only base64-encoded, not secret.
Code Examples
// Trusting the algorithm from the token header:
$decoded = JWT::decode($token, $secret); // No algorithm allowlist specified
// Attacker sends alg=none token — some libraries accept it
// Whitelist algorithm — reject none and algorithm confusion:
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
// Always specify algorithm explicitly:
$decoded = JWT::decode(
$token,
new Key($publicKey, 'RS256') // Only RS256 accepted — rejects HS256, none
);
// Validate all critical claims:
if ($decoded->iss !== 'https://auth.example.com') {
throw new Exception('Invalid issuer');
}
if ($decoded->aud !== 'api.example.com') {
throw new Exception('Invalid audience');
}
if ($decoded->exp < time()) {
throw new Exception('Token expired');
}