jose, jwk-to-pem, node-forge, node-jose, pem, and pem-jwk are JavaScript libraries that handle cryptographic operations related to JSON Web Tokens (JWT), JSON Web Keys (JWK), and Privacy-Enhanced Mail (PEM) formats. These tools enable developers to sign, verify, encrypt, and decrypt tokens, as well as convert between key formats like JWK and PEM. While some focus exclusively on modern JOSE standards (e.g., jose), others provide broader cryptographic utilities (node-forge) or format conversion helpers (jwk-to-pem, pem-jwk). Note that node-jose is officially deprecated and should not be used in new projects.
When building secure web applications—especially those involving OAuth, OpenID Connect, or API authentication—you’ll often need to work with JSON Web Tokens (JWT), JSON Web Keys (JWK), and PEM-encoded cryptographic material. The six packages under review (jose, jwk-to-pem, node-forge, node-jose, pem, pem-jwk) all touch this space, but they differ significantly in scope, maintenance status, and suitability for modern frontend architectures. Let’s break down what each does—and doesn’t—offer.
node-jose Is RetiredFirst, the critical update: node-jose is officially deprecated. Its npm page states: "This library is no longer actively maintained. Please consider using the jose library instead." It hasn’t seen meaningful updates since 2021 and uses outdated cryptographic patterns. Do not use it in new projects. If you’re maintaining an old codebase that relies on it, plan a migration to jose.
| Package | JWT Sign/Verify | JWK ↔ PEM | Browser Support | Native Web Crypto | Full JOSE (JWS/JWE) |
|---|---|---|---|---|---|
jose | ✅ | ✅ | ✅ | ✅ | ✅ |
jwk-to-pem | ❌ | JWK→PEM | ❌ (Node-only) | ❌ | ❌ |
node-forge | ❌ (manual only) | ✅ (manual) | ✅ | ❌ | ❌ |
node-jose | ✅ (deprecated) | ✅ | ✅ (legacy) | ❌ | ✅ (outdated) |
pem | ❌ | PEM→keys | ❌ (Node-only) | ❌ | ❌ |
pem-jwk | ❌ | ↔ | ❌ (Node-only) | ❌ | ❌ |
Now let’s see how these differences play out in real code.
Imagine you’ve fetched a public JWK from an identity provider (like Auth0 or Azure AD) and need to verify a JWT in the browser.
jose (Modern, Recommended)// Works in browser and Node.js
import { jwtVerify, importJWK } from 'jose';
const jwk = {
kty: 'RSA',
e: 'AQAB',
n: '...' // long modulus
};
const jwt = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...';
const publicKey = await importJWK(jwk);
const { payload } = await jwtVerify(jwt, publicKey);
console.log(payload);
This uses the native Web Crypto API under the hood—no polyfills, no extra dependencies.
node-jose (Deprecated — Avoid)// Do not use in new code
const jose = require('node-jose');
const store = jose.JWK.createKeyStore();
await store.add(jwk);
const verified = await jose.JWS.createVerify(store).verify(jwt);
console.log(JSON.parse(verified.payload));
While functional, this approach is obsolete and lacks modern security hardening.
jwk-to-pem + Node.js crypto (Legacy Node Pattern)// Node.js only — won't work in browsers
const jwkToPem = require('jwk-to-pem');
const crypto = require('crypto');
const pem = jwkToPem(jwk);
const verified = crypto.verify(
'sha256',
Buffer.from(jwtHeader + '.' + jwtPayload),
pem,
signatureBuffer
);
This requires manual JWT parsing and signature verification—error-prone and verbose. Not suitable for frontend apps.
Sometimes you need to convert key formats—for example, to interoperate with legacy systems.
pem-jwk// Node.js only
const { pem2jwk, jwk2pem } = require('pem-jwk');
const privateKey = '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----';
const jwk = pem2jwk(privateKey); // → JWK object
const pem = jwk2pem(jwk); // → PEM string
Simple, but limited to basic RSA/EC keys and only runs in Node.js.
jose (Preferred for New Code)// Works everywhere
import { exportJWK, importPKCS8 } from 'jose';
// PEM to JWK
const pkcs8 = pem.replace(/-----[^-]+-----/g, '').replace(/\s/g, '');
const key = await importPKCS8(`-----BEGIN PRIVATE KEY-----\n${pkcs8}\n-----END PRIVATE KEY-----`, 'RS256');
const jwk = await exportJWK(key);
// JWK to PEM (via export)
// Note: jose doesn't output PEM directly, but you can base64-decode the raw key
While jose doesn’t have a one-liner for PEM output, it encourages safer key handling via CryptoKey objects.
node-forge (Manual, Flexible)// Browser or Node.js
const forge = require('node-forge');
// PEM to JWK (simplified example for RSA)
const privateKey = forge.pki.privateKeyFromPem(pemString);
const n = forge.util.encode64(privateKey.n.toByteArray());
const e = forge.util.encode64(privateKey.e.toByteArray());
const jwk = { kty: 'RSA', n, e };
This gives you full control but requires deep knowledge of ASN.1 and key structures. Easy to get wrong.
If you need to generate keys or self-signed certs (e.g., for testing or internal tooling), pem and node-forge shine.
pem (Node.js Only)const pem = require('pem');
pem.createCertificate({ days: 365, selfSigned: true }, (err, keys) => {
console.log(keys.certificate); // PEM cert
console.log(keys.serviceKey); // PEM private key
});
Great for DevOps scripts, but useless in the browser.
node-forgeconst forge = require('node-forge');
const keys = forge.pki.rsa.generateKeyPair(2048);
const cert = forge.pki.createCertificate();
// ... configure cert fields, sign, etc.
const pem = forge.pki.certificateToPem(cert);
More verbose, but works in browsers—useful for client-side certificate generation in specialized apps (e.g., enterprise PKI tools).
Frontend developers care about this: which libraries actually run in the browser?
jose: Fully browser-compatible. Uses Web Crypto API. Tree-shakable.node-forge: Pure JavaScript, works everywhere—but large bundle (~200KB minified).jwk-to-pem, pem, pem-jwk: Rely on Node.js built-ins (Buffer, crypto). Will fail in browsers unless heavily polyfilled.node-jose: Technically works in browsers but is deprecated and bloated.If you’re building a React, Vue, or Svelte app that verifies tokens client-side, jose is your only sane choice among these.
jose is actively maintained by Filip Skokan, a core contributor to OAuth/OpenID specs. It follows current best practices (e.g., disallowing insecure algorithms by default).node-forge is stable but rarely updated. Its broad scope increases attack surface—only use what you need.pem-family tools (jwk-to-pem, pem, pem-jwk) are narrowly focused but unmaintained or minimally updated. They assume you know exactly what you’re doing.josepempem-jwk or jwk-to-pem (if Node-only)jose throughout for consistency.node-forgejosepem-jwk or jwk-to-pem (but plan to migrate)node-forgenode-joseThe ecosystem has matured: jose now covers most needs with better security, smaller size, and cross-platform support. Reserve the older tools for niche interoperability tasks—and always validate your threat model before handling keys in client-side code.
Choose jose if you need a modern, spec-compliant, and actively maintained library for full JWT/JWS/JWE operations with native Web Crypto support in both Node.js and browsers. It’s ideal for applications requiring up-to-date security practices and minimal dependencies.
Choose jwk-to-pem only if you’re working in a legacy Node.js environment that requires converting JWKs to PEM format for use with older crypto APIs, and you cannot migrate to modern alternatives like jose. Avoid in new browser-based projects.
Choose node-forge if you need a pure JavaScript implementation of TLS, PKI, and general cryptography that works in both Node.js and browsers without native dependencies. It’s useful when you must generate keys, manage certificates, or perform low-level crypto outside standard JOSE workflows.
Do not choose node-jose for new projects — it is officially deprecated as of 2021. Migrate existing implementations to jose or another modern alternative due to lack of maintenance and outdated cryptographic practices.
Choose pem if your primary need is generating or parsing PEM-encoded RSA/DSA/ECC keys and certificates in Node.js, especially for server-side tooling or internal infrastructure. It does not support JWK or JWT operations directly.
Choose pem-jwk only for simple bidirectional conversion between PEM and JWK in Node.js environments where you already rely on this utility and don’t require full JWT validation or signing. Prefer jose for end-to-end token handling.
jose is a JavaScript module for JSON Object Signing and Encryption, providing support for JSON Web Tokens (JWT), JSON Web Signature (JWS), JSON Web Encryption (JWE), JSON Web Key (JWK), JSON Web Key Set (JWKS), and more. The module is designed to work across various Web-interoperable runtimes including Node.js, browsers, Cloudflare Workers, Deno, Bun, and others.
If you want to quickly add JWT authentication to JavaScript apps, feel free to check out Auth0's JavaScript SDK and free plan. Create an Auth0 account; it's free!
Support from the community to continue maintaining and improving this module is welcome. If you find the module useful, please consider supporting the project by becoming a sponsor.
jose has no dependencies and it exports tree-shakeable ESM1.
jose is distributed via npmjs.com, jsr.io, jsdelivr.com, and github.com.
example ESM import1
import * as jose from 'jose'
The jose module supports JSON Web Tokens (JWT) and provides functionality for signing and verifying tokens, as well as their JWT Claims Set validation.
jwtVerify function
SignJWT classThe jose module supports encrypted JSON Web Tokens and provides functionality for encrypting and decrypting tokens, as well as their JWT Claims Set validation.
jwtDecrypt functionEncryptJWT classThe jose module supports importing, exporting, and generating keys and secrets in various formats, including PEM formats like SPKI, X.509 certificate, and PKCS #8, as well as JSON Web Key (JWK).
The jose module supports signing and verification of JWS messages with arbitrary payloads in Compact, Flattened JSON, and General JSON serialization syntaxes.
The jose module supports encryption and decryption of JWE messages with arbitrary plaintext in Compact, Flattened JSON, and General JSON serialization syntaxes.
The following are additional features and utilities provided by the jose module:
The jose module is compatible with JavaScript runtimes that support the utilized Web API globals and standard built-in objects or are Node.js.
The following runtimes are supported (this is not an exhaustive list):
Please note that certain algorithms may not be available depending on the runtime used. You can find a list of available algorithms for each runtime in the specific issue links provided above.
| Version | Security Fixes 🔑 | Other Bug Fixes 🐞 | New Features ⭐ | Runtime and Module type |
|---|---|---|---|---|
| v6.x | Security Policy | ✅ | ✅ | Universal2 ESM1 |
| v5.x | Security Policy | ❌ | ❌ | Universal2 CJS + ESM |
| v4.x | Security Policy | ❌ | ❌ | Universal2 CJS + ESM |
| v2.x | Security Policy | ❌ | ❌ | Node.js CJS |
The algorithm implementations in jose have been tested using test vectors from their respective specifications as well as RFC7520.