@sourceregistry/node-jwt
v1.5.6
Published
A lightweight, zero-dependency TypeScript library for creating, verifying and decoding JSON Web Tokens (JWT).
Maintainers
Readme
🔐 @sourceregistry/node-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:
- Tiny
- Secure by default
- TypeScript-first with full JSDoc
- Zero external dependencies
- 100% test coverage (Trying😉)
- Dual API: Sync and Promise-based
- Automatic algorithm detection based on key type
- Full JWK/JWKS support (
import/export,toPublicJWK,x5c/x5t, RFC 7638 thumbprints, kid-based key selection)
📦 Installation
npm install @sourceregistry/node-jwtRequires Node.js ≥ 16
🚀 Quick Start
Sync API (default)
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);Promise API (/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);🧠 Algorithm Autodetection (New)
When options.alg is omitted, the library automatically selects the correct JWT algorithm based on the signing key.
🔑 Autodetection Rules
| 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 Errors
Autodetection fails for unsupported keys:
- Unsupported EC curve
- Unsupported RSA-PSS hash algorithm (e.g.
sha1) - Unsupported asymmetric key type (e.g. DSA)
🔑 Supported Algorithms
| 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.
🧩 JWK / JWKS Support
- Import/export JWK:
importJWK(),exportJWK() - Convert to public-only JWK:
toPublicJWK() - Compute RFC 7638 thumbprint:
getJWKThumbprint() - Support x5c/x5t (X.509 cert chain + SHA-1 thumbprint)
- Normalize JWKS with auto-generated
kidandx5t - Resolve keys from JWKS by
kidfor verification - Load remote JWKS with caching via
JWKS.fromWeb()
🔹 Example: JWKS Key Selection
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);🔹 Example: Remote JWKS (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 (0disables automatic refresh)timeoutMs— network timeout in msendpointOverride— custom endpoint (absolute or relative)overrideEndpointCheck— skip automatic/.well-known/jwks.jsonappendcache— custom cache backend with{ get(key), set(key, value) }
fromWeb() resolver methods:
key(kid)— returns a normalized key (orundefined) extended withtoKeyObject()list()— returns all normalized keysfind(query)— returns normalized matching keysfindFirst(query)— returns first normalized match orundefinedrefresh()— forces reload and returns the resolver instanceexport()— returns the current cached JWKS snapshot
🔹 Local Examples
See runnable examples in:
examples/jwt.example.tsexamples/jwks.example.ts
🛡️ Security Features
- ✅ Safe algorithm autodetection
- ✅ Strict algorithm whitelisting (
algorithmsoption) - ✅ Full RSASSA-PSS and Ed25519 support
- ✅ Time claim validation (
exp,nbf,iat) with clock skew - ✅ Claim validation (
iss,sub,aud,jti) - ✅ Maximum token age enforcement
- ✅ Timing-safe signature comparison
- ✅ No insecure defaults
✅ Production Checklist
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:
- Pin
algorithmsin every verify call (do not rely on implicit acceptance). - Always validate
issuerandaudiencefor external tokens. - Use short token lifetimes and enforce
maxTokenAge. - Rotate keys regularly and configure JWKS cache
ttlfor your threat model. - Monitor and alert on verification failures (
INVALID_SIGNATURE,INVALID_CLAIM,INVALID_OPTIONS).
🔏 ECDSA Signature Format: DER vs JOSE (New)
For ECDSA algorithms (ES256, ES384, ES512, ES256K) there are two common signature encodings:
- DER (ASN.1) — what Node.js produces by default
- JOSE (
r || sraw signature) — required by the JWT/JWS spec and used by systems like VAPID/Web Push (WNS)
Default behavior
By default, this library outputs DER signatures for ES* algorithms to match Node.js/OpenSSL defaults.
Enable JOSE output
To generate spec-compliant JOSE ECDSA signatures, set:
signatureFormat: "jose"insign()
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" });Auto-detect verification (optional)
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".
📚 API Reference
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).
🧪 Testing
- High branch coverage
- All algorithms + autodetection paths
- All failure modes
- Sync + Promise APIs
- Full JWK/JWKS coverage (import/export, x5c/x5t, thumbprint, kid selection)
npm test
npm run test:coverage📦 Exports
| Import | Description |
| ----------------------------------- | ----------- |
| @sourceregistry/node-jwt | Sync API |
| @sourceregistry/node-jwt/promises | Promise API |
🙌 Contributing
PRs welcome! Please add tests and maintain full coverage.
🔐 Security issues? Report responsibly: [email protected]
🔗 GitHub: https://github.com/SourceRegistry/node-jwt 📦 npm: https://www.npmjs.com/package/@sourceregistry/node-jwt
