The JWT Algorithm 'none' Attack: The Vulnerability in 1 Line of Code

Published: (December 31, 2025 at 12:53 AM EST)
2 min read
Source: Dev.to

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

CVELibraryIssue
CVE-2015-2951jwt‑simpleAlgorithm confusion
CVE-2016-10555jose2goNone algorithm bypass
CVE-2018-0114node‑joseKey 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

RuleCWEWhat it catches
no-algorithm-noneCWE‑347Algorithm "none" allowed
no-algorithm-confusionCWE‑327RS/HS confusion attacks
no-weak-secretCWE‑326Brute‑forceable secrets
no-hardcoded-secretCWE‑798Secrets in code
no-sensitive-payloadCWE‑312PII in tokens
require-expirationCWE‑613Missing exp claim
require-algorithm-whitelistCWE‑327No explicit algorithms
require-issuer-validationCWE‑345Missing iss check
require-audience-validationCWE‑345Missing aud check
no-decode-without-verifyCWE‑347jwt.decode() misuse
require-issued-atCWE‑613Missing iat claim
require-max-ageCWE‑613No maxAge in verify
no-timestamp-manipulationCWE‑345Clock 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?

GitHub | LinkedIn

Back to Blog

Related posts

Read more »