mrkeyoor.com_
Wed 05 Aug 05:01 UTC
npmSecurityupdated 04 Aug 2026

jsonwebtoken

jsonwebtoken is Auth0's Node.js implementation of JSON Web Tokens (RFC 7519). You call jwt.sign() to create a signed token from a payload plus a secret or private key, and jwt.verify() to check the signature and standard claims like exp, aud, and iss on the way back in. It supports HMAC, RSA, RSA-PSS, and ECDSA algorithms, and it is the package nearly every Node auth tutorial reaches for, which is why it still moves 54M downloads a week.

Verdict

It works and half the Node auth code on the internet depends on it, but this is maintenance-mode software with a multi-year release gap. For new projects jose is the better default; pick jsonwebtoken mainly for consistency with an existing codebase.

API stability5/5sign, verify, and decode have kept the same shape across v8 and v9; v9 mostly tightened security defaults like minimum RSA key sizes.
Docs4/5The README documents every sign and verify option with examples and lists all error types; there is no dedicated docs site but the surface is small enough not to need one.
Maintenance2/5One release between August 2023 and December 2025, 206 open issues and PRs, and a stated TODO that X.509 certificate chains are not checked. Alive, but barely.
Ecosystem5/5About 54M weekly downloads and near-universal presence in Node auth guides, express middleware, and boilerplates.

Use it if

  • You need to sign and verify JWTs in a plain Node.js service and want the API every tutorial and Stack Overflow answer uses
  • You verify tokens against a JWKS endpoint; the getKey callback pairs directly with jwks-rsa
  • You want claim validation (exp, nbf, aud, iss, sub, maxAge, clockTolerance) handled by options instead of hand-rolled checks
Skip it if

Setup reality

npm install jsonwebtoken works everywhere Node runs, but it drags in ten dependencies including five lodash.* micro-packages and a full copy of semver, which is a lot for a token signer. The API is CommonJS with sync-or-callback style and no promises, so you either use the sync form or wrap it in util.promisify. TypeScript types live in the separate @types/jsonwebtoken package. Coming from v8, read the v9 migration notes: RSA keys under 2048 bits are rejected unless you explicitly opt out with allowInsecureKeySizes.

Patterns

Sign a token with a shared secret (HS256)sign-hs256

const jwt = require('jsonwebtoken');

const token = jwt.sign({ sub: 'user-123', role: 'admin' }, process.env.JWT_SECRET);

HS256 is the default algorithm; an iat (issued at) claim is added automatically unless you pass noTimestamp.

Sign a token that expiressign-with-expiry

const jwt = require('jsonwebtoken');

const token = jwt.sign({ sub: 'user-123' }, process.env.JWT_SECRET, {
  expiresIn: '1h'
});

A bare number means seconds but a numeric string like '120' means milliseconds; always include units in string values.

Sign with an RSA private key (RS256)sign-rs256

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

const privateKey = fs.readFileSync('private.key');
const token = jwt.sign({ sub: 'user-123' }, privateKey, { algorithm: 'RS256' });

Since v9, RSA keys under 2048 bits throw unless you set allowInsecureKeySizes: true, which you should not.

Verify a token and read its payloadverify-token

const jwt = require('jsonwebtoken');

try {
  const decoded = jwt.verify(token, process.env.JWT_SECRET);
  console.log(decoded.sub);
} catch (err) {
  // TokenExpiredError, JsonWebTokenError, or NotBeforeError
}

The README warns to treat the decoded payload like any other user input; verify checks the signature, not your business rules.

Pin the allowed algorithms on verifypin-allowed-algorithms

const jwt = require('jsonwebtoken');

const decoded = jwt.verify(token, publicKey, {
  algorithms: ['RS256']
});

Always pin algorithms when verifying; accepting whatever the token header claims is the classic JWT confusion attack.

Verify audience, issuer, and subjectverify-claims

const jwt = require('jsonwebtoken');

const decoded = jwt.verify(token, publicKey, {
  audience: 'urn:my-api',
  issuer: 'https://issuer.example.com/',
  subject: 'user-123',
  clockTolerance: 5
});

clockTolerance (in seconds) absorbs small clock drift between servers when checking exp and nbf.

Distinguish an expired token from an invalid onehandle-expired-token

const jwt = require('jsonwebtoken');

try {
  jwt.verify(token, secret);
} catch (err) {
  if (err.name === 'TokenExpiredError') {
    console.log('expired at', err.expiredAt);
  } else if (err.name === 'JsonWebTokenError') {
    console.log('invalid:', err.message);
  }
}

Expired tokens usually mean 'refresh', invalid tokens usually mean 'reject'; branching on err.name is how you tell them apart.

Peek into a token without verifying itdecode-without-verify

const jwt = require('jsonwebtoken');

const decoded = jwt.decode(token, { complete: true });
console.log(decoded.header.alg, decoded.header.kid);
console.log(decoded.payload);

decode() does not check the signature at all; never make auth decisions from it, only routing decisions like picking a key by kid.

Verify against a remote JWKS endpointverify-with-jwks

const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');

const client = jwksClient({
  jwksUri: 'https://tenant.auth0.com/.well-known/jwks.json'
});

function getKey(header, callback) {
  client.getSigningKey(header.kid, (err, key) => {
    if (err) return callback(err);
    callback(null, key.publicKey || key.rsaPublicKey);
  });
}

jwt.verify(token, getKey, { algorithms: ['RS256'] }, (err, decoded) => {
  if (err) return console.error(err);
  console.log(decoded.sub);
});

The getKey callback form only works with the async verify signature; the sync form cannot fetch keys.

Use verify with async/awaitpromisify-verify

const util = require('util');
const jwt = require('jsonwebtoken');

const verifyAsync = util.promisify(jwt.verify);

const decoded = await verifyAsync(token, process.env.JWT_SECRET);

The library predates promises and never added them; promisify works because the callback is error-first.

Alternatives

PackageRegistryPick it when
josenpmWhen you need edge or browser support, EdDSA, JWE, or a modern promise-based API
fast-jwtnpmWhen token sign and verify throughput actually shows up in your profiles