
Security News
The Hidden Blast Radius of the Axios Compromise
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.
@sourceregistry/node-jwt
Advanced tools
A lightweight, zero-dependency TypeScript library for creating, verifying and decoding JSON Web Tokens (JWT).
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 verificationJWKS.fromWeb()import { 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);
fromWeb)import { JWKS } from '@sourceregistry/node-jwt';
const jwks = await JWKS.fromWeb('https://issuer.example', {
ttl: 60_000,
timeoutMs: 2_000
});
const jwk = await jwks.key('my-kid');
const keyObject = jwk?.toKeyObject();
const keys = await jwks.list();
const rsaKeys = await jwks.find({ kty: 'RSA' });
const firstSigKey = await jwks.findFirst({ use: 'sig' });
// Force refresh
await jwks.refresh(); // returns resolver for chaining
// Access cached JWKS snapshot
const current = jwks.export();
fromWeb() options:
fetch — custom fetch implementation (for runtimes/framework adapters)ttl — cache TTL in ms (0 disables automatic refresh)timeoutMs — network timeout in msendpointOverride — custom endpoint (absolute or relative)overrideEndpointCheck — skip automatic /.well-known/jwks.json appendcache — custom cache backend with { get(key), set(key, value) }fromWeb() resolver methods:
key(kid) — returns a normalized key (or undefined) extended with toKeyObject()list() — returns all normalized keysfind(query) — returns normalized matching keysfindFirst(query) — returns first normalized match or undefinedrefresh() — forces reload and returns the resolver instanceexport() — returns the current cached JWKS snapshotSee runnable examples in:
examples/jwt.example.tsexamples/jwks.example.tsalgorithms option)exp, nbf, iat) with clock skewiss, sub, aud, jti)Use this profile in production verification paths:
import { verify } from '@sourceregistry/node-jwt';
const result = verify(token, publicKeyOrSecret, {
algorithms: ['RS256'], // pin expected algorithm(s)
issuer: 'https://issuer.example',
audience: 'my-service',
clockSkew: 30, // seconds
maxTokenAge: 3600 // seconds
});
Recommended operational checks:
algorithms in every verify call (do not rely on implicit acceptance).issuer and audience for external tokens.maxTokenAge.ttl for your threat model.INVALID_SIGNATURE, INVALID_CLAIM, INVALID_OPTIONS).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
FAQs
A lightweight, zero-dependency TypeScript library for creating, verifying and decoding JSON Web Tokens (JWT).
We found that @sourceregistry/node-jwt demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.