The JWT Algorithm 'none' Attack: The Vulnerability in 1 Line of Code
Source: Dev.to
Attack demonstration
// ❌ This looks fine...
const decoded = jwt.verify(token, secret, {
algorithms: ['HS256', 'none'], // 💀 The vulnerability
});
1. Attacker takes a valid JWT
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTYiLCJyb2xlIjoidXNlciJ9.
signature_here
2. Modifies the header to use the none algorithm
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.
eyJzdWIiOiIxMjM0NTYiLCJyb2xlIjoiYWRtaW4ifQ.
// No signature needed!
3. Server accepts it because "none" is in the algorithms list
The attacker is now admin.
Known CVEs
| CVE | Library | Issue |
|---|---|---|
| CVE-2015-2951 | jwt‑simple | Algorithm confusion |
| CVE-2016-10555 | jose2go | None algorithm bypass |
| CVE-2018-0114 | node‑jose | Key confusion |
Secure configuration examples
// ✅ Explicitly whitelist algorithms
const decoded = jwt.verify(token, secret, {
algorithms: ['HS256'], // Only what you use!
});
// ❌ Dangerous: allowing "none"
jwt.verify(token, secret, { algorithms: ['none'] });
// ✅ Safe: explicit RS256 verification
jwt.verify(token, publicKey, { algorithms: ['RS256'] });
// ❌ Dangerous: brute‑forceable secret
jwt.sign(payload, 'password123');
// ✅ Safe: strong secret (256+ bits)
jwt.sign(payload, process.env.JWT_SECRET);
// ❌ Dangerous: token never expires
jwt.sign({ userId: 123 }, secret);
// ✅ Safe: short expiration
jwt.sign({ userId: 123 }, secret, { expiresIn: '1h' });
// ❌ Dangerous: password in token (tokens can be decoded!)
jwt.sign({ userId: 123, password: 'secret' }, key);
// ✅ Safe: only IDs in payload
jwt.sign({ userId: 123 }, key);
ESLint plugin for JWT security
The eslint-plugin-jwt rule set enforces best practices and catches common pitfalls.
Rules overview
| Rule | CWE | What it catches |
|---|---|---|
no-algorithm-none | CWE‑347 | Algorithm "none" allowed |
no-algorithm-confusion | CWE‑327 | RS/HS confusion attacks |
no-weak-secret | CWE‑326 | Brute‑forceable secrets |
no-hardcoded-secret | CWE‑798 | Secrets in code |
no-sensitive-payload | CWE‑312 | PII in tokens |
require-expiration | CWE‑613 | Missing exp claim |
require-algorithm-whitelist | CWE‑327 | No explicit algorithms |
require-issuer-validation | CWE‑345 | Missing iss check |
require-audience-validation | CWE‑345 | Missing aud check |
no-decode-without-verify | CWE‑347 | jwt.decode() misuse |
require-issued-at | CWE‑613 | Missing iat claim |
require-max-age | CWE‑613 | No maxAge in verify |
no-timestamp-manipulation | CWE‑345 | Clock skew exploits |
Example lint error
src/auth.ts
15:3 error 🔒 CWE-347 CVSS:9.8 | JWT algorithm 'none' is allowed
Risk: Attackers can forge tokens without a signature
Fix: Remove 'none' from algorithms: ['HS256']
Installation
npm install eslint-plugin-jwt
// .eslintrc.js
import jwtPlugin from 'eslint-plugin-jwt';
export default [jwtPlugin.configs.recommended];
13 rules. Full JWT security. Zero false positives.
⭐ Star on GitHub.
Take action
🚀 Check your JWT configuration now. Is "none" in your algorithms list?