A minimal, secure, and production-ready JWT (JSON Web Token) library for Node.js with zero dependencies. Supports all standard signing algorithms (HMAC, RSA, ECDSA, EdDSA, RSASSA-PSS), automatic algorithm detection, JWK/JWKS, and full claim validation.
β¨ Why another JWT library? Most JWT libraries are bloated, have security pitfalls, or lack proper TypeScript support. This library is:
import/export, toPublicJWK, x5c/x5t, RFC 7638 thumbprints, kid-based key selection)npm install @sourceregistry/node-jwt
Requires Node.js β₯ 16
import { sign, verify, decode } from '@sourceregistry/node-jwt';
// Sign (algorithm auto-detected)
const token = sign(
{ sub: '1234567890', name: 'John Doe', iat: Math.floor(Date.now() / 1000) },
'your-secret-key'
);
// Verify
const result = verify(token, 'your-secret-key', { issuer: 'https://example.com' });
if (result.valid) {
console.log('Payload:', result.payload);
} else {
console.error('JWT Error:', result.error.code, result.error.reason);
}
// Decode (unsafe)
const { header, payload, signature } = decode(token);
/promises)import { sign, verify, decode } from '@sourceregistry/node-jwt/promises';
// Sign (algorithm auto-detected)
const token = await sign(
{ sub: '1234567890', name: 'John Doe', iat: Math.floor(Date.now() / 1000) },
'your-secret-key'
);
// Verify
try {
const { payload } = await verify(token, 'your-secret-key', {
issuer: 'https://example.com',
audience: 'my-app',
algorithms: ['HS256']
});
console.log('Payload:', payload);
} catch (error) {
console.error('JWT Error:', error.code, error.reason);
}
// Decode (unsafe)
const { header, payload, signature } = await decode(token);
When options.alg is omitted, the library automatically selects the correct JWT algorithm based on the signing key.
| Key Type | Detection Logic | Selected Algorithm |
|---|---|---|
Symmetric (string / Buffer) |
Default HMAC | HS256 |
| RSA private key | PKCS#1 v1.5 | RS256 |
| RSA-PSS private key | Hash algorithm in key | PS256 / PS384 / PS512 |
EC P-256 (prime256v1) |
Curve name | ES256 |
EC P-384 (secp384r1) |
Curve name | ES384 |
EC P-521 (secp521r1) |
Curve name | ES512 |
| EC secp256k1 | Curve name | ES256K |
| Ed25519 | Key type | EdDSA |
π‘ Node.js exposes OpenSSL curve names (
prime256v1,secp384r1, etc.). These are automatically normalized to JOSE algorithms.
Autodetection fails for unsupported keys:
sha1)| Algorithm | Type | Secret Type |
|---|---|---|
| HS256 / HS384 / HS512 | HMAC | string | Buffer |
| RS256 / RS384 / RS512 | RSA | Private / Public key |
| PS256 / PS384 / PS512 | RSA-PSS | Private / Public key |
| ES256 / ES384 / ES512 | ECDSA | Private / Public key |
| ES256K | ECDSA (secp256k1) | Private / Public key |
| EdDSA | Ed25519 | Private / Public key |
Keys may be PEM, DER, JWK, or Node.js
KeyObject.
importJWK(), exportJWK()toPublicJWK()getJWKThumbprint()kid and x5tkid for verificationimport { JWKS, JWK } from '@sourceregistry/node-jwt';
const keyPair = generateKeyPairSync('rsa', { modulusLength: 2048 });
const jwk = JWK.toPublic(keyPair.publicKey);
const jwks = JWKS.normalize({ keys: [jwk] });
// Retrieve key by kid
const keyObject = JWKS.toKeyObject(jwks, jwk.kid);
algorithms option)exp, nbf, iat) with clock skewiss, sub, aud, jti)For ECDSA algorithms (ES256, ES384, ES512, ES256K) there are two common signature encodings:
r || s raw signature) β required by the JWT/JWS spec and used by systems like VAPID/Web Push (WNS)By default, this library outputs DER signatures for ES* algorithms to match Node.js/OpenSSL defaults.
To generate spec-compliant JOSE ECDSA signatures, set:
signatureFormat: "jose" in sign()import { sign, verify } from "@sourceregistry/node-jwt";
const token = sign(
{ sub: "123", iat: Math.floor(Date.now() / 1000) },
ecPrivateKey,
{ alg: "ES256", signatureFormat: "jose" }
);
// Verify JOSE-signed token
const result = verify(token, ecPublicKey, { signatureFormat: "jose" });
If enabled in your version, verify() can also validate JOSE ECDSA signatures without specifying signatureFormat (it will try DER first, then JOSE).
If you want strict behavior, pass signatureFormat: "der" or signatureFormat: "jose" explicitly.
π‘ For VAPID/Web Push (e.g. Windows WNS endpoints), you typically need
ES256withsignatureFormat: "jose".
sign(payload, secret, options?)alg (optional) β If omitted, algorithm is auto-detectedkid β Key IDtyp β Token type (default: "JWT")verify(token, secret, options?)Includes algorithm whitelist protection and full claim validation.
Error Codes include:
INVALID_TOKENINVALID_ALGORITHMALGORITHM_NOT_ALLOWEDINVALID_SIGNATURETOKEN_EXPIREDTOKEN_NOT_ACTIVETOKEN_TOO_OLDMISSING_* / INVALID_*decode(token)Decode a JWT without verification (unsafe).
npm test
npm run test:coverage
| Import | Description |
|---|---|
@sourceregistry/node-jwt |
Sync API |
@sourceregistry/node-jwt/promises |
Promise API |
PRs welcome! Please add tests and maintain full coverage.
π Security issues? Report responsibly: a.p.a.slaa@projectsource.nl
π GitHub: https://github.com/SourceRegistry/node-jwt π¦ npm: https://www.npmjs.com/package/@sourceregistry/node-jwt