Description
Allowing the unsigned JWT algorithm none for authentication can make an application accept attacker-modified payloads. In jsonwebtoken 9.x, an empty key alone does not automatically skip signature verification. The before example explicitly allows none in algorithms alongside an empty key. Trusting identity or permission claims without verifying a signature may enable authentication or authorization bypass.
Potential impact
- Forged tokens may impersonate an authenticated user.
- Altered
roleoradminclaims may grant excessive permissions. - An attacker may access protected APIs and sensitive data.
- Impersonation may allow ongoing use of another user's resources.
Remediation
- Always supply a valid trusted secret or public key to
verify(). - Specify an algorithm allow-list and exclude
'none', for example{ algorithms: ["HS256"] }or{ algorithms: ["RS256"] }. decode()only parses a token. Do not trust its result for authentication or authorization; useverify()with a trusted key.- Validate claims such as
iss(issuer),aud(audience), andexp(expiration). If expiration is required, also require theexpclaim to be present.
Examples
Before
javascript
const express = require("express");
const jwt = require("jsonwebtoken");
const app = express();
// Before: verify without a key (for example, an empty string)
app.get("/profile", (req, res) => {
const auth = req.headers.authorization || "";
const token = auth.replace(/^Bearer\s+/i, "");
// BAD: an empty key and explicit none allowance permit unsigned tokens
const payload = jwt.verify(token, "", { algorithms: ["HS256", "none"] });
// Forged payloads may gain access
res.json({ user: payload.sub, admin: payload.admin });
});
app.listen(3000);
After
javascript
const express = require("express");
const jwt = require("jsonwebtoken");
const app = express();
const JWT_SECRET = process.env.JWT_SECRET; // Store securely
app.get("/profile", (req, res) => {
const m = (req.headers.authorization || "").match(/^Bearer\s+(.+)$/i);
if (!m) return res.status(401).send("Missing token");
try {
const payload = jwt.verify(m[1], JWT_SECRET, {
algorithms: ["HS256"], // Allow only the required algorithm, excluding none
issuer: "auth.example.com", // Validate iss
audience: "my-api", // Validate aud
});
return res.json({ user: payload.sub });
} catch (e) {
return res.status(401).send("Invalid token");
}
});
app.listen(3000);
Explanation:
- Before: An empty key combined with
noneinalgorithmspermits unsigned tokens. An attacker-controlled payload may be trusted as authenticated identity data. - After: A trusted key and a restricted algorithm list exclude
none. The handler also checksissandaudand rejects validation failures. ConfigureJWT_SECRETwith a strong secret. Expiration is checked whenexpis present, but this excerpt does not require that claim to exist.