alg: "none" is a JWT header value that means "this token has no
signature — don't verify one." It exists for unsecured tokens. It is also a
full authentication bypass the moment your verify call accepts it:
// ❌ one entry in this list is a forged-token machine
jwt.verify(token, secret, { algorithms: ["HS256", "none"] });
The attack: change one header field
A JWT is three base64url parts: header.payload.signature. The attacker takes
any valid token and edits the header to claim no algorithm, rewrites the
payload to whatever they want, and drops the signature entirely:
# header → {"alg":"none","typ":"JWT"}
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0
# payload → {"sub":"123456","role":"admin"}
.eyJzdWIiOiIxMjM0NTYiLCJyb2xlIjoiYWRtaW4ifQ
# signature → (empty)
.
No private key, no brute force. Because "none" is in your algorithms list,
the verifier skips signature checking, reads "role":"admin", and waves the
attacker through. CWE-347 — Improper Verification of Cryptographic Signature.
It's not theoretical
This is the 2015 disclosure (Tim McLean, "Critical vulnerabilities in JSON Web
Token libraries") that forced the whole ecosystem to harden. Modern
jsonwebtoken won't accept alg:none unless you explicitly allow it — which
is exactly the misconfiguration that still ships: someone adds "none" to the
algorithms array to make a test pass, or to support a legacy unsigned token,
and forgets it's there.
The fix: pin the algorithms you actually use
// ✅ only the algorithm you sign with — "none" can never match
jwt.verify(token, secret, { algorithms: ["HS256"] });
An explicit, minimal algorithms list is the whole defense: if "none" (and
the keys you don't use) can't appear, the bypass can't happen.
The rule: no-algorithm-none (CWE-347)
You won't catch a stray "none" in a 200-file codebase by eye. The linter fails
the build on it:
npm install --save-dev eslint-plugin-jwt
// eslint.config.mjs — `configs` is a NAMED export (default export is the plugin)
import { configs } from "eslint-plugin-jwt";
export default [configs.recommended];
src/auth.ts
15:3 error 🔒 CWE-347 | Using alg:"none" bypasses signature verification, allowing token forgery | CRITICAL
Fix: Remove "none" and use RS256, ES256, or other secure algorithms
(The ESLint CLI also appends the rule's doc URL to the Fix: line; it's trimmed
here for width.) The rule flags "none" anywhere in an algorithms array —
case-insensitively, and whether it's the only entry or buried in a list like the
one above.
The cousin: algorithm confusion
alg:none has a subtler relative. If you verify with no pinned algorithm and
hand the library your RS256 public key, an attacker re-signs a token with
that public key using HS256 — and the library, treating the public key as an
HMAC secret, accepts it:
jwt.verify(token, publicKey); // ❌ no algorithms list → RS256 verified as HS256
jwt.verify(token, publicKey, { algorithms: ["RS256"] }); // ✅ pinned
Same root cause, same fix: always pass an explicit algorithms list.
no-algorithm-confusion and require-algorithm-whitelist enforce it. Those —
plus secret strength, claim validation (exp/iss/aud), and the
jwt.decode() trap — are the other 12 rules, walked end to end in the
eslint-plugin-jwt getting-started.
Install
# npm
npm install --save-dev eslint-plugin-jwt
# yarn
yarn add -D eslint-plugin-jwt
# pnpm
pnpm add -D eslint-plugin-jwt
# bun
bun add -d eslint-plugin-jwt
# CI — block the PR on a re-introduced "none"
- run: npx eslint . --max-warnings 0
Compatibility
| Surface | Support |
|---|---|
| Package managers | npm, yarn, pnpm, bun |
| Node | >= 18.0.0 |
| ESLint | `^8.0.0 \ |
| JWT libraries | detects {% raw %}jsonwebtoken and jose call shapes (sign/verify/decode) — reads source |
| Module system | Plugin ships CommonJS; your config can be eslint.config.js or .mjs
|
| Oxlint | Loads under Oxlint's JS-plugin runner via the interlace-jwt port, parity-gated in CI |
Links
- 📦 npm: eslint-plugin-jwt
- 📖 Rule docs: no-algorithm-none
- 📚 The full 13-rule JWT walkthrough
- 💻 Source on GitHub
⭐ Star on GitHub if a verify call in your codebase has ever listed "none".
I'm Ofri Peretz, a security engineering leader and the author of the
Interlace ESLint ecosystem — domain-specific static analysis for security,
reliability, and performance on the Node.js stack. eslint-plugin-jwt is its
JWT/auth layer.
Top comments (0)