mrkeyoor.com_
Sat 19 Sept 10:48 UTC
npmSecurityupdated 19 Sept 2026

jsonwebtoken review

jsonwebtoken 9.0.3 creates and validates signed JWTs with Node's crypto implementation. Its verify call can enforce the signature algorithm, expiration, activation time, maximum age, issuer, audience, subject, nonce, and token ID before returning claims. HMAC, RSA, RSA-PSS, and ECDSA families are supported; encryption and the wider JOSE feature set are outside the package. The 9.0.3 release updates jws to 4.0.1. Our browser build failed, and package inspection found no TypeScript declarations, which makes this a Node JavaScript dependency rather than a cross-runtime token toolkit.

51.3Mdownloads / wk
Verdict

jsonwebtoken 9.0.3 installed in 0.9 seconds with 0 audit findings, but our browser build failed and the package shipped no TypeScript types. It remains a practical fit for established Node services using synchronous signing or callback JWKS lookup; new ESM, edge, encrypted-token, or typed projects should start with jose.

We installed it

Lab card: what happened when we installed jsonwebtokenScreenshot of jsonwebtoken documentation
Install✓ · 0.9s15 packages on disk · 1 MB
ImportESM import works · require() works · CommonJS package
Browsern/acould not be bundled for the browser (Node-only code, most likely)
Typesno TypeScript types found
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does jsonwebtoken install cleanly?

Yes. In a fresh container with an empty cache, npm install jsonwebtoken finished in 0.9s, leaving 15 packages and 1 MB on disk. npm audit reported no known vulnerabilities.

Can jsonwebtoken run in a browser?

Not directly: esbuild could not bundle it for the browser in our run, which normally means it depends on Node built-ins. Use it on the server, or find a browser-targeted alternative.

Does jsonwebtoken work with both ESM and CommonJS?

Yes. Both import 'jsonwebtoken' and require('jsonwebtoken') worked in Node 22 in our run. The package is published as CommonJS.

Does jsonwebtoken include TypeScript types?

No type declarations were found in our install, so TypeScript users need their own declarations.

jsonwebtoken or jose: which should you use?

jose: Choose it for promise-based ESM across Node, browsers, and edge runtimes, including JWE or EdDSA. jsonwebtoken 9.0.3 installed in 0.9 seconds with 0 audit findings, but our browser build failed and the package shipped no TypeScript types.

When should you not use jsonwebtoken?

The verifier runs in a browser, Worker, or edge isolate. Our esbuild browser build failed against this Node crypto package.

API stability5/5`sign`, `verify`, and `decode` retain the synchronous return or callback shape used through version 9. The major release removed several unsafe compatibility defaults, including acceptance paths for unsigned tokens, small RSA keys, and wrong asymmetric key types. Patch 9.0.3 changes jws resolution without changing claims, options, returned values, or error classes.
Docs4/5The README documents every sign and verify option, its algorithm table, NumericDate, asynchronous key lookup, decode warnings, and the three named verification error families. It directly explains that a unitless duration string means milliseconds and that verified claims remain hostile input. There is no separate reference site, and teams must source TypeScript, promise wrappers, JWKS caching, rotation, and token policy elsewhere.
Maintenance2/5GitHub shows an unarchived repository pushed on June 25, 2026 with 208 open issues and pull requests. Release 9.0.3 arrived on December 4, 2025 to update jws after 9.0.2 had remained current since August 2023. Dependency and security maintenance continues, but that cadence gives teams little reason to expect prompt feature additions or rapid resolution of ordinary issues.
Ecosystem5/5npm recorded 56,864,991 downloads in the latest completed week, and GitHub reports 18,189 stars. Express middleware, Auth0 examples, JWKS clients, and years of Node authentication code use its callbacks and named error classes. CommonJS require and Node ESM import both worked in our sandbox, which helps mixed server repositories despite the missing exports map and bundled TypeScript declarations.

Discussed on

  1. hnWriting my first Rust crate: jsonwebtoken7 points
  2. hnAuth0 fixes RCE flaw in JsonWebToken library used by 22,000 projects4 points
  3. hnThe Future of Jwt.io3 points
  4. hnSecurity Issue in JWT Secret Poisoning3 points

Use it if

  • A Node service already depends on jsonwebtoken's synchronous returns, thrown errors, or error-first callbacks.
  • HMAC, RSA, RSA-PSS, or ECDSA signatures must be created or checked synchronously.
  • One verification call should enforce token age plus issuer, audience, subject, nonce, or clock tolerance.
  • An existing JWKS client can resolve the selected public key through the `getKey` callback.
Skip it if

Setup reality

We installed jsonwebtoken 9.0.3 in a fresh Node 22 Bookworm sandbox. npm completed in 0.9 seconds, leaving 15 packages and 1 MB on disk. The package itself is 100 KB unpacked, declares 10 direct dependencies and 0 peers, and carries an MIT license. npm audit reported 0 known vulnerabilities. Node >=12 and npm >=6 are declared. We found no bundled TypeScript types.

This is CommonJS without an exports map. require() and ESM import both worked through Node interop, while esbuild could not create a browser bundle. Keep it on the server and store private keys or HMAC secrets outside the repository. Version 9 rejects RSA keys below 2048 bits and asymmetric key-type mismatches unless explicit compatibility flags weaken those checks.

Without a callback, sign and verify block and return or throw. Adding a callback selects the error-first asynchronous API; there is no promise result. Remote JWKS resolution therefore needs the callback form or a wrapper. Treat the header's kid as hostile, let a JWKS client resolve it, restrict algorithms, and verify issuer plus audience. decode performs no signature check and cannot support authorization.

A numeric expiresIn is seconds, while a string without a unit is parsed as milliseconds. Prefer 900 or '15m', never '900' when you mean 15 minutes. Do not place a standard claim in both the payload and its sign option. A valid signature proves who signed the bytes, not that each returned property has the type or permission your application expects.

Patterns

Issue a 15-minute HMAC token sign-hmac-token

const jwt = require('jsonwebtoken');

const token = jwt.sign(
  { sub: user.id, scope: ['orders:read'] },
  process.env.JWT_SECRET,
  {
    algorithm: 'HS256',
    issuer: 'https://auth.example.com',
    audience: 'orders-api',
    expiresIn: '15m',
  },
);

Generate the secret from random bytes and restrict the algorithm explicitly. The `m` suffix prevents 15 from being parsed as milliseconds.

Create an RS256 token sign-rsa-token

const fs = require('node:fs');
const jwt = require('jsonwebtoken');

const privateKey = fs.readFileSync('/run/secrets/jwt-private.pem');
const token = jwt.sign({ sub: 'user-42' }, privateKey, {
  algorithm: 'RS256',
  keyid: '2026-08-primary',
  expiresIn: '10m',
});

RSA moduli under 2,048 bits fail in version 9 unless an unsafe override is used. `keyid` identifies the public key during rotation.

Verify signature and intended recipient verify-expected-claims

const jwt = require('jsonwebtoken');

const claims = jwt.verify(token, publicKey, {
  algorithms: ['RS256'],
  issuer: 'https://auth.example.com',
  audience: 'orders-api',
  clockTolerance: 5,
});

if (typeof claims.sub !== 'string') {
  throw new Error('missing subject');
}

These options validate token context as well as cryptography. Apply a schema to returned claims before using roles or identifiers.

Branch on verification error classes handle-verification-errors

const jwt = require('jsonwebtoken');

try {
  return jwt.verify(token, key, verifyOptions);
} catch (error) {
  if (error instanceof jwt.TokenExpiredError) return { status: 'expired' };
  if (error instanceof jwt.NotBeforeError) return { status: 'too-early' };
  if (error instanceof jwt.JsonWebTokenError) return { status: 'invalid' };
  throw error;
}

Use class names and their `expiredAt` or `date` properties. Error message strings are unsuitable as a stable interface.

Inspect key ID before verification decode-token-header

const jwt = require('jsonwebtoken');

const decoded = jwt.decode(token, { complete: true });
if (!decoded || typeof decoded === 'string') {
  throw new Error('malformed token');
}
console.log(decoded.header.kid, decoded.header.alg);

`decode` authenticates nothing. Treat `kid` as untrusted lookup input and enforce an allowlisted key source.

Verify through a JWKS callback verify-with-key-callback

const jwt = require('jsonwebtoken');

function getKey(header, callback) {
  if (header.alg !== 'RS256' || typeof header.kid !== 'string') {
    return callback(new Error('unexpected JWT header'));
  }
  keyStore.get(header.kid, callback);
}

jwt.verify(
  token,
  getKey,
  { algorithms: ['RS256'], issuer, audience },
  (error, claims) => {
    if (error) return next(error);
    req.auth = claims;
    next();
  },
);

Supplying a key function requires the callback form of `verify`. Add caching, timeouts, and request bounds in the JWKS client.

Set activation and expiry windows set-not-before

const jwt = require('jsonwebtoken');

const token = jwt.sign({ sub: job.id }, secret, {
  notBefore: '30s',
  expiresIn: '5m',
  jwtid: crypto.randomUUID(),
});

Both relative windows are based on `iat`. Supplying `nbf` or `exp` again inside the payload is rejected.

Enforce maximum token age limit-token-age

const claims = jwt.verify(token, key, {
  algorithms: ['RS256'],
  maxAge: '20m',
  clockTolerance: 5,
});

`maxAge` reads `iat`, so the issuer must include a trustworthy issued-at value for this policy.

Recover random bytes from base64 use-base64-hmac-secret

const jwt = require('jsonwebtoken');

const secret = Buffer.from(process.env.JWT_SECRET_B64, 'base64');
const claims = jwt.verify(token, secret, {
  algorithms: ['HS256'],
});

Decode only when configuration stores base64 for raw random bytes. Using the encoded characters themselves produces another HMAC key.

Bind an ID token to its nonce verify-nonce

const claims = jwt.verify(idToken, providerPublicKey, {
  algorithms: ['RS256'],
  issuer: providerIssuer,
  audience: clientId,
  nonce: session.expectedNonce,
});

Check nonce beside issuer and audience, then consume the stored expected value so it cannot be replayed.

Open an encrypted signing key sign-with-passphrase-key

const jwt = require('jsonwebtoken');

const token = jwt.sign(payload, {
  key: encryptedPrivateKey,
  passphrase: process.env.JWT_KEY_PASSPHRASE,
}, {
  algorithm: 'RS256',
  expiresIn: '10m',
});

When the key arrives as `{ key, passphrase }`, `algorithm` must be present rather than inferred.

Import CommonJS from an ESM module import-from-esm

import jwt from 'jsonwebtoken';

const token = jwt.sign(
  { sub: 'user-42' },
  process.env.JWT_SECRET,
  { expiresIn: '10m' },
);

Our Node 22 ESM probe succeeded through interop. Version 9.0.3 still lacks an exports map and packaged declarations.

Alternatives

PackageRegistryPick it when
josenpmChoose it for promise-based ESM across Node, browsers, and edge runtimes, including JWE or EdDSA.
fast-jwtnpmChoose it after benchmarking shows signing or verification throughput is the limiting path.
njwtnpmChoose it only where an existing service already follows nJwt's key and claim conventions.

More security guides

cryptography · pyjwt · jose · dompurify · requests-oauthlib · oauthlib · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.