Safeguard
Open Source

jsonwebtoken npm: Security Review and Safe Usage

The jsonwebtoken npm package signs and verifies JWTs for countless Node apps, and versions at or below 8.5.1 carry serious verification flaws. Here is how to use npm jsonwebtoken safely.

Aisha Rahman
Security Analyst
5 min read

The jsonwebtoken npm package is the most widely used library for creating and verifying JSON Web Tokens in Node.js, and versions at or below 8.5.1 contain several flaws in jwt.verify() that can undermine token security, all fixed in version 9.0.0. Because npm jsonwebtoken sits directly on your authentication path, an issue here is not a nuisance dependency bug, it is a potential authentication bypass, which is why the December 2022 advisory batch got so much attention.

Getting this one right is worth the few minutes it takes.

What jsonwebtoken does

A JSON Web Token is a signed, self-contained credential: a header, a payload of claims, and a signature. jsonwebtoken handles the two operations that matter, jwt.sign() to issue a token and jwt.verify() to validate one. The security of the whole scheme rests on verify being strict about the signature and the algorithm, because a token whose signature is not properly checked is just an attacker-editable cookie.

That is exactly where the historical vulnerabilities lived.

The pre-9.0.0 vulnerabilities

In December 2022, Auth0 (the maintainer) released jsonwebtoken 9.0.0 to address four CVEs, all affecting versions at or below 8.5.1:

  • CVE-2022-23529: insecure input validation in jwt.verify() that, under specific conditions, could be exploited when a secret or public key was passed in an unexpected form.
  • CVE-2022-23539: the library allowed insecure legacy key types to be used for signature verification.
  • CVE-2022-23540: an insecure default algorithm in jwt.verify() that could lead to a signature-validation bypass. Version 9.0.0 removed the implicit acceptance of the none algorithm.
  • CVE-2022-23541: an insecure key-retrieval implementation that could permit forged tokens by confusing RSA and HMAC key handling.

The common thread is the "algorithm confusion" family of attacks. If a verifier does not pin the expected algorithm, an attacker can sometimes present a token signed with a different algorithm, or the none algorithm, and slip past verification. Version 9.0.0 tightened these defaults, and the remediation is simply to upgrade.

The fix and the safe usage pattern

First, upgrade:

npm install jsonwebtoken@^9.0.0

Then, regardless of version, verify tokens defensively. The most important rule is to always specify the exact algorithm you expect. Never let the library infer it from the token itself, because that is what enables algorithm-confusion attacks:

const jwt = require('jsonwebtoken');

// Good: pin the algorithm and set an expiry check
try {
  const payload = jwt.verify(token, publicKey, {
    algorithms: ['RS256'],   // explicit allowlist, not inferred
    issuer: 'https://auth.example.com',
    audience: 'my-api',
    maxAge: '15m'
  });
  // payload is trustworthy here
} catch (err) {
  // reject: signature, expiry, issuer, or audience failed
}

The algorithms option is the single most important line. Passing an explicit allowlist means a token claiming a different algorithm is rejected outright. Adding issuer and audience checks ensures a valid token minted for another service cannot be replayed against yours.

For signing, keep secrets strong and out of source control:

const token = jwt.sign(
  { sub: user.id, role: user.role },
  privateKey,
  { algorithm: 'RS256', expiresIn: '15m' }
);

Prefer asymmetric algorithms (RS256, ES256) when the verifier and signer are different services, so the verifier only needs the public key. Reserve symmetric HS256 for cases where one party both signs and verifies.

Common mistakes beyond the CVEs

Upgrading fixes the library flaws, but most JWT incidents come from how the library is used:

  • Decoding instead of verifying. jwt.decode() reads the payload without checking the signature. It is for inspection only. Using it to make authorization decisions means trusting attacker-supplied claims. Always use jwt.verify() for anything security-relevant.
  • No expiry. Tokens without expiresIn live forever. Keep access tokens short and use refresh tokens for longevity.
  • Secrets in the repo. An HS256 secret committed to Git lets anyone forge valid tokens. Load keys from a secret manager or environment configuration.
  • Trusting alg from the token. Covered above, but worth repeating: pin algorithms in verify.

Keeping the dependency current

jsonwebtoken is often transitive, pulled in by auth middleware and SDKs, so a vulnerable copy can exist even if you install version 9.x directly. Check the whole tree:

npm ls jsonwebtoken
npm why jsonwebtoken

Continuous scanning is what keeps this honest over time. An SCA tool such as Safeguard flags a jsonwebtoken at or below 8.5.1 anywhere in your dependency graph and can gate a build on it, so an old copy dragged in by a transitive parent does not quietly reopen an authentication weakness. Given that this library sits on the auth path, it is worth setting a strict severity threshold for it specifically. Our academy has a walkthrough on JWT hardening that goes deeper on key rotation and revocation.

FAQ

Which jsonwebtoken version fixes the known vulnerabilities?

Version 9.0.0, released in December 2022, fixed CVE-2022-23529, CVE-2022-23539, CVE-2022-23540, and CVE-2022-23541. All affected versions at or below 8.5.1. Upgrade to 9.0.0 or later.

What is the most important option when verifying a JWT?

The algorithms allowlist. Always pass the exact algorithm you expect to jwt.verify() so a token claiming a different algorithm, or the none algorithm, is rejected. This closes the algorithm-confusion class of attacks.

Is jwt.decode() safe to use for authorization?

No. jwt.decode() reads the payload without verifying the signature, so its claims are attacker-controlled. Use it only for inspection. Always use jwt.verify() for any security decision.

How do I find a vulnerable jsonwebtoken pulled in transitively?

Run npm ls jsonwebtoken to list all copies and npm why jsonwebtoken to see which parent introduced an old one. Software composition analysis will flag copies at or below 8.5.1 anywhere in the resolved tree, including transitive ones.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.