jose vs jwk-to-pem vs node-forge vs node-jose vs pem vs pem-jwk
JavaScript Libraries for JWT, JWK, and PEM Cryptography in Web Applications
josejwk-to-pemnode-forgenode-josepempem-jwkSimilar Packages:

JavaScript Libraries for JWT, JWK, and PEM Cryptography in Web Applications

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.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
jose07,450258 kB23 days agoMIT
jwk-to-pem015522.6 kB12a year agoApache-2.0
node-forge05,2791.64 MB4564 months ago(BSD-3-Clause OR GPL-2.0)
node-jose0722353 kB713 years agoApache-2.0
pem0574338 kB203 years agoMIT
pem-jwk073-97 years agoMPL-2.0

Handling JWT, JWK, and PEM in JavaScript: A Practical Guide for Frontend Developers

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.

⚠️ Deprecation Alert: node-jose Is Retired

First, 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.

🔑 Core Capabilities at a Glance

PackageJWT Sign/VerifyJWK ↔ PEMBrowser SupportNative Web CryptoFull JOSE (JWS/JWE)
jose
jwk-to-pemJWK→PEM❌ (Node-only)
node-forge❌ (manual only)✅ (manual)
node-jose✅ (deprecated)✅ (legacy)✅ (outdated)
pemPEM→keys❌ (Node-only)
pem-jwk❌ (Node-only)

Now let’s see how these differences play out in real code.

🧪 Verifying a JWT Using a JWK

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.

With 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.

With 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.

With 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.

🔁 Converting Between JWK and PEM

Sometimes you need to convert key formats—for example, to interoperate with legacy systems.

Bidirectional Conversion with 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.

With 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.

With 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.

🛠️ Generating Keys and Certificates

If you need to generate keys or self-signed certs (e.g., for testing or internal tooling), pem and node-forge shine.

With 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.

With node-forge

const 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).

🌐 Browser Compatibility Reality Check

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.

🔒 Security and Maintenance

  • 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.
  • The 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.

💡 When to Use What: Real Scenarios

Scenario 1: SPA That Validates ID Tokens from Auth0

  • Use jose
  • Why? You need lightweight, browser-safe JWT verification with JWK support.

Scenario 2: Node.js Backend That Generates Certificates for Internal Services

  • Use pem
  • Why? Simple API for cert/key generation; no browser requirements.

Scenario 3: Legacy System That Accepts Only PEM Keys but Uses JWK Internally

  • Use pem-jwk or jwk-to-pem (if Node-only)
  • But consider refactoring to use jose throughout for consistency.

Scenario 4: Browser-Based PKI Tool That Creates CSRs

  • Use node-forge
  • Why? Only option that supports full certificate lifecycle in-browser.

📌 Final Recommendations

  • For 90% of modern JWT use cases (frontend or backend): jose
  • For legacy Node.js format conversion: pem-jwk or jwk-to-pem (but plan to migrate)
  • For full PKI in browser: node-forge
  • Never start a new project with node-jose

The 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.

How to Choose: jose vs jwk-to-pem vs node-forge vs node-jose vs pem vs pem-jwk

  • jose:

    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.

  • jwk-to-pem:

    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.

  • node-forge:

    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.

  • node-jose:

    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.

  • pem:

    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.

  • pem-jwk:

    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.

README for jose

jose

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.

Sponsor

Auth0 by Okta

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!

💗 Help the project

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.

Dependencies: 0

jose has no dependencies and it exports tree-shakeable ESM1.

Documentation

jose is distributed via npmjs.com, jsr.io, jsdelivr.com, and github.com.

example ESM import1

import * as jose from 'jose'

JSON Web Tokens (JWT)

The jose module supports JSON Web Tokens (JWT) and provides functionality for signing and verifying tokens, as well as their JWT Claims Set validation.

Encrypted JSON Web Tokens

The jose module supports encrypted JSON Web Tokens and provides functionality for encrypting and decrypting tokens, as well as their JWT Claims Set validation.

Key Utilities

The 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).

JSON Web Signature (JWS)

The jose module supports signing and verification of JWS messages with arbitrary payloads in Compact, Flattened JSON, and General JSON serialization syntaxes.

JSON Web Encryption (JWE)

The jose module supports encryption and decryption of JWE messages with arbitrary plaintext in Compact, Flattened JSON, and General JSON serialization syntaxes.

Other

The following are additional features and utilities provided by the jose module:

Supported Runtimes

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.

Supported Versions

VersionSecurity Fixes 🔑Other Bug Fixes 🐞New Features ⭐Runtime and Module type
v6.xSecurity PolicyUniversal2 ESM1
v5.xSecurity PolicyUniversal2 CJS + ESM
v4.xSecurity PolicyUniversal2 CJS + ESM
v2.xSecurity PolicyNode.js CJS

Specifications

Details

The algorithm implementations in jose have been tested using test vectors from their respective specifications as well as RFC7520.

Footnotes

  1. CJS style let jose = require('jose') is possible in Node.js versions where the require(esm) feature is enabled by default (^20.19.0 || ^22.12.0 || >= 23.0.0). 2 3

  2. Assumes runtime support of WebCryptoAPI and Fetch API 2 3