# jose `jose` is a JavaScript module implementing JSON Object Signing and Encryption (JOSE) standards — JWT, JWS, JWE, JWK, and JWKS — with zero dependencies and full tree-shaking support via ESM. It targets all Web-interoperable runtimes: Node.js (≥20.19/22.12/23+), browsers, Cloudflare Workers, Deno, Bun, and Electron. All cryptographic operations are performed through the Web Cryptography API (`SubtleCrypto`), making the module both portable and specification-compliant across RFC 7515/7516/7517/7518/7519/7638/9278. The library covers the full JOSE surface: signed JWTs (`SignJWT`/`jwtVerify`), encrypted JWTs (`EncryptJWT`/`jwtDecrypt`), raw JWS in Compact/Flattened/General serializations, raw JWE in the same three serializations, key generation/import/export for RSA, EC, OKP, symmetric, and post-quantum ML-DSA key types, local and remote JWKS resolvers, JWK thumbprint calculation, and a suite of typed error classes. Every API is `async` and returns standard `CryptoKey` or `Uint8Array` values. --- ## Key Generation ### `generateKeyPair(alg, options?)` — Generate an asymmetric key pair Generates a `{ privateKey, publicKey }` pair using the Web Crypto API for use with any asymmetric JWA algorithm (RS*, PS*, ES*, EdDSA/Ed25519, ECDH-ES*, ML-DSA-*). By default `privateKey` is non-extractable; pass `{ extractable: true }` to export it later. ```js import * as jose from 'jose' // EC key pair for ES256 const { publicKey, privateKey } = await jose.generateKeyPair('ES256') // RSA-PSS 4096-bit extractable key pair const { publicKey: rsaPub, privateKey: rsaPriv } = await jose.generateKeyPair('PS384', { modulusLength: 4096, extractable: true, }) console.log(await jose.exportPKCS8(rsaPriv)) console.log(await jose.exportSPKI(rsaPub)) // ECDH key pair with X25519 curve const { publicKey: ecdhPub, privateKey: ecdhPriv } = await jose.generateKeyPair('ECDH-ES', { crv: 'X25519', }) // Ed25519 key pair const { publicKey: edPub, privateKey: edPriv } = await jose.generateKeyPair('Ed25519') console.log(edPub) // CryptoKey { type: 'public', ... } ``` --- ### `generateSecret(alg, options?)` — Generate a symmetric secret Generates a symmetric key for HMAC (HS256/384/512), AES-GCM (A128/192/256GCM), AES-KW (A128/192/256KW), AES-GCMKW, or AES-CBC compound algorithms. Returns a `CryptoKey` for most, and a random `Uint8Array` for A128CBC-HS256 / A192CBC-HS384 / A256CBC-HS512. ```js import * as jose from 'jose' // HMAC secret for HS256 (non-extractable CryptoKey) const hmacSecret = await jose.generateSecret('HS256') console.log(hmacSecret) // CryptoKey { type: 'secret', ... } // AES-GCM key const aesKey = await jose.generateSecret('A256GCM', { extractable: true }) console.log(await jose.exportJWK(aesKey)) // AES-CBC compound secret (always returns Uint8Array) const cbcSecret = await jose.generateSecret('A256CBC-HS512') console.log(cbcSecret instanceof Uint8Array) // true console.log(cbcSecret.byteLength) // 64 ``` --- ## Key Import ### `importJWK(jwk, alg?, options?)` — Import a JSON Web Key Converts a JWK object into a `CryptoKey` (for RSA/EC/OKP/AKP) or `Uint8Array` (for `kty: "oct"`). The `alg` argument may be omitted if `jwk.alg` is present. ```js import * as jose from 'jose' // Import an EC public key const ecPublicKey = await jose.importJWK( { crv: 'P-256', kty: 'EC', x: 'ySK38C1jBdLwDsNWKzzBHqKYEE5Cgv-qjWvorUXk9fw', y: '_LeQBw07cf5t57Iavn4j-BqJsAD1dpoz8gokd3sBsOo', }, 'ES256', ) // Import an RSA public key const rsaPublicKey = await jose.importJWK( { kty: 'RSA', e: 'AQAB', n: '12oBZRhCiZFJLcPg59LkZZ9mdhSMTKAQZYq32k_ti5SBB6jerkh-WzOMAO664r_qyLkqHUSp3u5SbXtseZEpN3XPWGKSxjsy-1JyEFTdLSYe6f9gfrmxkUF_7DTpq0gn6rntP05g2-wFW50YO7mosfdslfrTJYWHFhJALabAeYirYD7-9kqq9ebfFMF4sRRELbv9oi36As6Q9B3Qb5_C1rAzqfao_PCsf9EPsTZsVVVkA5qoIAr47lo1ipfiBPxUCCNSdvkmDTYgvvRm6ZoMjFbvOtgyts55fXKdMWv7I9HMD5HwE9uW839PWA514qhbcIsXEYSFMPMV6fnlsiZvQQ', }, 'PS256', ) // Import a symmetric oct key (returns Uint8Array) const octKey = await jose.importJWK({ kty: 'oct', k: 'GawgguFyGrWKav7AX4VKUg' }) console.log(octKey instanceof Uint8Array) // true ``` --- ### `importSPKI(spki, alg, options?)` — Import a public key from PEM (SPKI) Parses a `-----BEGIN PUBLIC KEY-----` PEM string into a `CryptoKey`. ```js import * as jose from 'jose' const spki = `-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFlHHWfLk0gLBbsLTcuCrbCqoHqmM YJepMC+Q+Dd6RBmBiA41evUsNMwLeN+PNFqib+xwi9JkJ8qhZkq8Y/IzGg== -----END PUBLIC KEY-----` const ecPublicKey = await jose.importSPKI(spki, 'ES256') console.log(ecPublicKey.type) // 'public' ``` --- ### `importPKCS8(pkcs8, alg, options?)` — Import a private key from PEM (PKCS#8) Parses a `-----BEGIN PRIVATE KEY-----` PEM string into a `CryptoKey`. ```js import * as jose from 'jose' const pkcs8 = `-----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgiyvo0X+VQ0yIrOaN nlrnUclopnvuuMfoc8HHly3505OhRANCAAQWUcdZ8uTSAsFuwtNy4KtsKqgeqYxg l6kwL5D4N3pEGYGIDjV69Sw0zAt43480WqJv7HCL0mQnyqFmSrxj8jMa -----END PRIVATE KEY-----` const ecPrivateKey = await jose.importPKCS8(pkcs8, 'ES256') console.log(ecPrivateKey.type) // 'private' ``` --- ### `importX509(x509, alg, options?)` — Import a public key from an X.509 certificate Extracts and imports the SPKI public key from a PEM X.509 certificate string. ```js import * as jose from 'jose' const x509 = `-----BEGIN CERTIFICATE----- MIIBXjCCAQSgAwIBAgIGAXvykuMKMAoGCCqGSM49BAMCMDYxNDAyBgNVBAMMK3Np QXBNOXpBdk1VaXhXVWVGaGtjZXg1NjJRRzFyQUhXaV96UlFQTVpQaG8wHhcNMjEw OTE3MDcwNTE3WhcNMjIwNzE0MDcwNTE3WjA2MTQwMgYDVQQDDCtzaUFwTTl6QXZN VWl4V1VlRmhrY2V4NTYyUUcxckFIV2lfelJRUE1aUGhvMFkwEwYHKoZIzj0CAQYI KoZIzj0DAQcDQgAE8PbPvCv5D5xBFHEZlBp/q5OEUymq7RIgWIi7tkl9aGSpYE35 UH+kBKDnphJO3odpPZ5gvgKs2nwRWcrDnUjYLDAKBggqhkjOPQQDAgNIADBFAiEA 1yyMTRe66MhEXID9+uVub7woMkNYd0LhSHwKSPMUUTkCIFQGsfm1ecXOpeGOufAh v+A1QWZMuTWqYt+uh/YSRNDn -----END CERTIFICATE-----` const publicKey = await jose.importX509(x509, 'ES256') console.log(publicKey.type) // 'public' ``` --- ## Key Export ### `exportJWK(key)` — Export a key as a JSON Web Key object Exports a `CryptoKey`, `KeyObject`, or `Uint8Array` to a plain JWK object. ```js import * as jose from 'jose' const { publicKey, privateKey } = await jose.generateKeyPair('ES256', { extractable: true }) const publicJwk = await jose.exportJWK(publicKey) const privateJwk = await jose.exportJWK(privateKey) console.log(publicJwk) // { kty: 'EC', crv: 'P-256', x: '...', y: '...' } console.log(privateJwk) // { kty: 'EC', crv: 'P-256', x: '...', y: '...', d: '...' } ``` --- ### `exportSPKI(key)` / `exportPKCS8(key)` — Export public / private key as PEM Serializes a public key to SPKI PEM or a private key to PKCS#8 PEM. ```js import * as jose from 'jose' const { publicKey, privateKey } = await jose.generateKeyPair('RS256', { extractable: true }) const spkiPem = await jose.exportSPKI(publicKey) const pkcs8Pem = await jose.exportPKCS8(privateKey) console.log(spkiPem) // -----BEGIN PUBLIC KEY-----\nMIIBIjAN... console.log(pkcs8Pem) // -----BEGIN PRIVATE KEY-----\nMIIEvQIB... ``` --- ## Signed JWT (JWS-format JWT) ### `SignJWT` — Build and sign a compact JWT A builder class for constructing and signing Compact JWS format JWTs. Chain setter methods then call `.sign(key)`. ```js import * as jose from 'jose' // --- Symmetric secret (HS256) --- const secret = new TextEncoder().encode( 'cc7e0d44fd473002f1c42167459001140ec6389b7353f8088f4d9a95f2f596f2', ) const jwt = await new jose.SignJWT({ 'urn:example:claim': true, role: 'admin' }) .setProtectedHeader({ alg: 'HS256' }) .setIssuedAt() .setIssuer('urn:example:issuer') .setAudience('urn:example:audience') .setSubject('user-123') .setJti('unique-id-abc') .setExpirationTime('2h') .setNotBefore('0s') .sign(secret) console.log(jwt) // eyJhbGciOiJIUzI1NiJ9.eyJ1cm4... // --- Asymmetric key (RS256) --- const { publicKey, privateKey } = await jose.generateKeyPair('RS256') const jwtRS = await new jose.SignJWT({ sub: 'user-456' }) .setProtectedHeader({ alg: 'RS256' }) .setIssuedAt() .setExpirationTime('1d') .sign(privateKey) console.log(jwtRS) ``` --- ### `jwtVerify(jwt, key, options?)` — Verify and decode a signed JWT Verifies the JWS signature and validates all JWT claims. Returns `{ payload, protectedHeader }`. Accepts a static key, JWK, `Uint8Array`, or a dynamic key-resolver function (e.g., from `createRemoteJWKSet`). ```js import * as jose from 'jose' const secret = new TextEncoder().encode( 'cc7e0d44fd473002f1c42167459001140ec6389b7353f8088f4d9a95f2f596f2', ) const token = 'eyJhbGciOiJIUzI1NiJ9.eyJ1cm46ZXhhbXBsZTpjbGFpbSI6dHJ1ZSwiaWF0IjoxNjY5MDU2MjMxLCJpc3MiOiJ1cm46ZXhhbXBsZTppc3N1ZXIiLCJhdWQiOiJ1cm46ZXhhbXBsZTphdWRpZW5jZSJ9.C4iSlLfAUMBq--wnC6VqD9gEOhwpRZpoRarE0m7KEnI' try { const { payload, protectedHeader } = await jose.jwtVerify(token, secret, { issuer: 'urn:example:issuer', audience: 'urn:example:audience', // clockTolerance: '1 minute', // maxTokenAge: '1 hour', // requiredClaims: ['sub'], }) console.log(protectedHeader) // { alg: 'HS256' } console.log(payload) // { 'urn:example:claim': true, iat: ..., iss: ..., aud: ... } } catch (err) { if (err instanceof jose.errors.JWTExpired) { console.error('Token expired:', err.payload) } else if (err instanceof jose.errors.JWTClaimValidationFailed) { console.error(`Claim '${err.claim}' failed: ${err.reason}`) } else if (err instanceof jose.errors.JWSSignatureVerificationFailed) { console.error('Bad signature') } } // Using a remote JWKS (e.g., Google OAuth2) const JWKS = jose.createRemoteJWKSet(new URL('https://www.googleapis.com/oauth2/v3/certs')) const { payload: p2 } = await jose.jwtVerify(token, JWKS, { issuer: 'https://accounts.google.com', audience: 'my-client-id', }) ``` --- ## Encrypted JWT (JWE-format JWT) ### `EncryptJWT` — Build and encrypt a compact JWT Builder class for Compact JWE format JWTs. Requires both `alg` (key management) and `enc` (content encryption) header parameters. ```js import * as jose from 'jose' // Direct key agreement with A128CBC-HS256 const secret = jose.base64url.decode('zH4NRP1HMALxxCFnRZABFA7GOJtzU_gIj02alfL1lvI') const encryptedJwt = await new jose.EncryptJWT({ userId: 42, admin: false }) .setProtectedHeader({ alg: 'dir', enc: 'A128CBC-HS256' }) .setIssuedAt() .setIssuer('urn:example:issuer') .setAudience('urn:example:audience') .setExpirationTime('2h') .replicateIssuerAsHeader() .encrypt(secret) console.log(encryptedJwt) // eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiaXNzIjoidXJuOi4uLiJ9... // RSA-OAEP-256 + A256GCM const { publicKey, privateKey } = await jose.generateKeyPair('RSA-OAEP-256') const encryptedRsa = await new jose.EncryptJWT({ sub: 'user-99' }) .setProtectedHeader({ alg: 'RSA-OAEP-256', enc: 'A256GCM' }) .setIssuedAt() .setExpirationTime('30m') .encrypt(publicKey) const { payload } = await jose.jwtDecrypt(encryptedRsa, privateKey) console.log(payload.sub) // 'user-99' ``` --- ### `jwtDecrypt(jwt, key, options?)` — Decrypt and validate an encrypted JWT Decrypts a Compact JWE JWT and validates its claims set. Returns `{ payload, protectedHeader }`. ```js import * as jose from 'jose' const secret = jose.base64url.decode('zH4NRP1HMALxxCFnRZABFA7GOJtzU_gIj02alfL1lvI') const encryptedJwt = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0..MB66qstZBPxAXKdsjet_lA.WHbtJTl4taHp7otOHLq3hBvv0yNPsPEKHYInmCPdDDeyV1kU-f-tGEiU4FxlSqkqAT2hVs8_wMNiQFAzPU1PUgIqWCPsBrPP3TtxYsrtwagpn4SvCsUsx0Mhw9ZhliAO8CLmCBQkqr_T9AcYsz5uZw.7nX9m7BGUu_u1p1qFHzyIg' try { const { payload, protectedHeader } = await jose.jwtDecrypt(encryptedJwt, secret, { issuer: 'urn:example:issuer', audience: 'urn:example:audience', // keyManagementAlgorithms: ['dir'], // contentEncryptionAlgorithms: ['A128CBC-HS256'], }) console.log(protectedHeader) // { alg: 'dir', enc: 'A128CBC-HS256' } console.log(payload) // { 'urn:example:claim': true, iat: ..., ... } } catch (err) { if (err instanceof jose.errors.JWEDecryptionFailed) { console.error('Decryption failed – wrong key?') } else if (err instanceof jose.errors.JWTExpired) { console.error('Token expired') } } ``` --- ## Raw JWS ### `CompactSign` — Sign an arbitrary payload as Compact JWS Creates a `header.payload.signature` string for any binary payload. ```js import * as jose from 'jose' const { privateKey } = await jose.generateKeyPair('ES256') const jws = await new jose.CompactSign( new TextEncoder().encode("It's a dangerous business, Frodo, going out your door."), ) .setProtectedHeader({ alg: 'ES256' }) .sign(privateKey) console.log(jws) // eyJhbGciOiJFUzI1NiJ9.SXQncyBhIGRhbmdlcm91cw... // Verify const { payload, protectedHeader } = await jose.compactVerify(jws, publicKey) console.log(new TextDecoder().decode(payload)) // "It's a dangerous business, Frodo, going out your door." ``` --- ### `GeneralSign` — Sign with multiple keys (General JWS) Produces a JSON object with a `signatures` array, allowing the same payload to be signed by multiple keys/algorithms simultaneously. ```js import * as jose from 'jose' const { privateKey: ecPriv } = await jose.generateKeyPair('ES256') const { privateKey: rsaPriv } = await jose.generateKeyPair('PS256') const jws = await new jose.GeneralSign( new TextEncoder().encode('Hello, General JWS!'), ) .addSignature(ecPriv) .setProtectedHeader({ alg: 'ES256' }) .addSignature(rsaPriv) .setProtectedHeader({ alg: 'PS256' }) .sign() console.log(JSON.stringify(jws, null, 2)) // { // "signatures": [ // { "protected": "eyJhbGciOiJFUzI1NiJ9", "signature": "..." }, // { "protected": "eyJhbGciOiJQUzI1NiJ9", "signature": "..." } // ], // "payload": "SGVsbG8sIEdlbmVyYWwgSldTIQ" // } ``` --- ## Raw JWE ### `CompactEncrypt` — Encrypt arbitrary plaintext as Compact JWE Encrypts a `Uint8Array` payload into a five-part `header.key.iv.ciphertext.tag` JWE token. ```js import * as jose from 'jose' const { publicKey, privateKey } = await jose.generateKeyPair('RSA-OAEP-256') const jwe = await new jose.CompactEncrypt( new TextEncoder().encode("It's a dangerous business, Frodo, going out your door."), ) .setProtectedHeader({ alg: 'RSA-OAEP-256', enc: 'A256GCM' }) .encrypt(publicKey) console.log(jwe) // eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIn0... // Decrypt const { plaintext, protectedHeader } = await jose.compactDecrypt(jwe, privateKey) console.log(new TextDecoder().decode(plaintext)) // "It's a dangerous business, Frodo, going out your door." console.log(protectedHeader) // { alg: 'RSA-OAEP-256', enc: 'A256GCM' } ``` --- ## JWKS ### `createLocalJWKSet(jwks)` — Verify using an in-memory JWKS Returns a key-resolver function that selects the correct key from a local `{ keys: [...] }` object, matched by `kid`, `alg`, `use`, and curve. ```js import * as jose from 'jose' const JWKS = jose.createLocalJWKSet({ keys: [ { kty: 'EC', crv: 'P-256', x: 'ySK38C1jBdLwDsNWKzzBHqKYEE5Cgv-qjWvorUXk9fw', y: '_LeQBw07cf5t57Iavn4j-BqJsAD1dpoz8gokd3sBsOo', alg: 'ES256', kid: 'my-ec-key-1', }, { kty: 'RSA', e: 'AQAB', n: '12oBZRhCi...', alg: 'PS256', kid: 'my-rsa-key-2', }, ], }) try { const { payload, protectedHeader } = await jose.jwtVerify(token, JWKS, { issuer: 'urn:example:issuer', audience: 'urn:example:audience', }) console.log(payload) } catch (err) { if (err?.code === 'ERR_JWKS_MULTIPLE_MATCHING_KEYS') { // iterate candidates when multiple keys match for await (const key of err) { try { return await jose.jwtVerify(token, key, { issuer: 'urn:example:issuer' }) } catch {} } } } ``` --- ### `createRemoteJWKSet(url, options?)` — Verify using a remote JWKS URL Downloads, caches, and auto-refreshes a JWKS from an HTTP(S) endpoint. Integrates with OAuth 2.0 / OIDC `jwks_uri` endpoints. ```js import * as jose from 'jose' // Basic usage with Google's OIDC keys const JWKS = jose.createRemoteJWKSet(new URL('https://www.googleapis.com/oauth2/v3/certs'), { timeoutDuration: 5000, // ms, default 5000 cooldownDuration: 30000, // ms between re-fetches cacheMaxAge: 600_000, // ms max cache age headers: { 'X-Custom': 'value' }, }) const { payload } = await jose.jwtVerify(idToken, JWKS, { issuer: 'https://accounts.google.com', audience: 'my-oauth-client-id', }) console.log(payload.sub) // Google user ID // Custom fetch (e.g., for proxies or retries via undici) import * as undici from 'undici' const proxyAgent = new undici.EnvHttpProxyAgent() const JWKSProxy = jose.createRemoteJWKSet(new URL('https://auth.example.com/.well-known/jwks.json'), { // @ts-ignore [jose.customFetch]: (...args) => // @ts-ignore undici.fetch(args[0], { ...args[1], dispatcher: proxyAgent }), }) // With persistent JWKS cache (for stateless edge runtimes) const cache = (await loadFromKVStore()) || {} const JWKSCached = jose.createRemoteJWKSet(new URL('https://auth.example.com/jwks'), { [jose.jwksCache]: cache, }) await jose.jwtVerify(jwt, JWKSCached) if (cache.uat !== oldUat) await saveToKVStore(cache) ``` --- ## JWK Utilities ### `calculateJwkThumbprint(key, digestAlgorithm?)` — Compute RFC 7638 thumbprint Returns a base64url-encoded SHA-256/384/512 digest of the canonical JWK members. ```js import * as jose from 'jose' // From a raw JWK const thumbprint = await jose.calculateJwkThumbprint( { kty: 'EC', crv: 'P-256', x: 'jJ6Flys3zK9jUhnOHf6G49Dyp5hah6CNP84-gY-n9eo', y: 'nhI6iD5eFXgBTLt_1p3aip-5VbZeMhxeFSpjfEAf7Ww', }, 'sha256', ) console.log(thumbprint) // 'w9eYdC6_s_tLQ8lH6PUpc0mddazaqtPgeC2IgWDiqY8' // From a CryptoKey const { publicKey } = await jose.generateKeyPair('ES256', { extractable: true }) const kpThumbprint = await jose.calculateJwkThumbprint(publicKey) console.log(kpThumbprint) // base64url string // Thumbprint URI (RFC 9278) const uri = await jose.calculateJwkThumbprintUri(publicKey) console.log(uri) // 'urn:ietf:params:oauth:jwk-thumbprint:sha-256:...' ``` --- ### `EmbeddedJWK` — Verify using a JWK embedded in the JWS header A ready-made `GetKeyFunction` that extracts and imports the public key from the `"jwk"` JWS header parameter. Always restrict accepted algorithms when using this. ```js import * as jose from 'jose' const jwt = 'eyJqd2siOnsiY3J2IjoiUC0yNTYiLCJ4IjoiVU05ZzVuS25aWFlvdldBbE03NmNMejl2VG96UmpfX0NIVV9kT2wtZ09vRSIsInkiOiJkczhhZVF3MWwyY0RDQTdiQ2tPTnZ3REtwWEFidFhqdnFDbGVZSDhXc19VIiwia3R5IjoiRUMifSwiYWxnIjoiRVMyNTYifQ.eyJpc3MiOiJ1cm46ZXhhbXBsZTppc3N1ZXIiLCJhdWQiOiJ1cm46ZXhhbXBsZTphdWRpZW5jZSIsImlhdCI6MTYwNDU4MDc5NH0.60boak3_dErnW47ZPty1C0nrjeVq86EN_eK0GOq6K8w2OA0thKoBxFK4j-NuU9yZ_A9UKGxPT_G87DladBaV9g' const { payload, protectedHeader } = await jose.jwtVerify(jwt, jose.EmbeddedJWK, { issuer: 'urn:example:issuer', audience: 'urn:example:audience', algorithms: ['ES256'], // IMPORTANT: restrict to known-safe algorithms }) console.log(protectedHeader) console.log(payload) ``` --- ## Utility Functions ### `decodeJwt(jwt)` — Decode JWT payload without verification Decodes the base64url payload of a Compact JWS JWT without signature verification. Useful for inspecting claims before cryptographic validation. ```js import * as jose from 'jose' const token = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyLTEyMyIsImlhdCI6MTY5MDAwMDAwMH0.sig' const claims = jose.decodeJwt(token) console.log(claims) // { sub: 'user-123', iat: 1690000000 } // With generic type interface MyClaims { sub: string; role: string } const typed = jose.decodeJwt(token) console.log(typed.role) ``` --- ### `decodeProtectedHeader(token)` — Decode a JOSE protected header Decodes the protected header from any JOSE token (Compact JWS, Compact JWE, or JSON serialization) without verifying any signature or decrypting. ```js import * as jose from 'jose' // From compact JWS or JWT const header = jose.decodeProtectedHeader('eyJhbGciOiJFUzI1NiIsImtpZCI6ImtleS0xIn0.payload.sig') console.log(header) // { alg: 'ES256', kid: 'key-1' } // From compact JWE (5 parts) const jweHeader = jose.decodeProtectedHeader( 'eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIn0.key.iv.cipher.tag', ) console.log(jweHeader) // { alg: 'RSA-OAEP-256', enc: 'A256GCM' } // From Flattened JSON JWS const flatHeader = jose.decodeProtectedHeader({ protected: 'eyJhbGciOiJFUzI1NiJ9', payload: '...', signature: '...' }) console.log(flatHeader) // { alg: 'ES256' } ``` --- ### `UnsecuredJWT` — Encode/decode `alg: "none"` JWTs For scenarios where signing is not required. Encodes with `.encode()` (synchronous) and decodes with the static `.decode()` method. ```js import * as jose from 'jose' // Encode const unsecuredJwt = new jose.UnsecuredJWT({ 'urn:example:claim': true }) .setIssuedAt() .setIssuer('urn:example:issuer') .setAudience('urn:example:audience') .setExpirationTime('2h') .encode() console.log(unsecuredJwt) // eyJhbGciOiJub25lIn0.eyJ1cm4... // Decode with optional claims validation const { payload, header } = jose.UnsecuredJWT.decode(unsecuredJwt, { issuer: 'urn:example:issuer', audience: 'urn:example:audience', }) console.log(header) // { alg: 'none' } console.log(payload) // { 'urn:example:claim': true, ... } ``` --- ### `base64url` — Base64URL encode/decode utilities Low-level helpers for encoding strings/bytes to base64url and decoding back to `Uint8Array`. ```js import * as jose from 'jose' // Encode string or Uint8Array const encoded = jose.base64url.encode('Hello, World!') console.log(encoded) // 'SGVsbG8sIFdvcmxkIQ' // Encode binary data const bytes = new Uint8Array([72, 101, 108, 108, 111]) console.log(jose.base64url.encode(bytes)) // 'SGVsbG8' // Decode back to Uint8Array const decoded = jose.base64url.decode('SGVsbG8sIFdvcmxkIQ') console.log(new TextDecoder().decode(decoded)) // 'Hello, World!' // Common pattern: create a symmetric secret from a base64url string const secret = jose.base64url.decode('zH4NRP1HMALxxCFnRZABFA7GOJtzU_gIj02alfL1lvI') ``` --- ## Error Handling ### `jose.errors` — Typed JOSE error classes All JOSE errors extend `JOSEError` and expose a stable `.code` string, enabling precise error handling without relying on message strings. ```js import * as jose from 'jose' try { await jose.jwtVerify(token, key, { issuer: 'expected-issuer' }) } catch (err) { // Catch all JOSE errors if (err instanceof jose.errors.JOSEError) { console.log(err.code) // e.g. 'ERR_JWT_EXPIRED' console.log(err.message) // human-readable message } // Specific checks via error code (tree-shakeable, no instanceof) switch (err?.code) { case 'ERR_JWT_EXPIRED': // err.payload, err.claim, err.reason available console.error('Token expired:', err.payload.exp) break case 'ERR_JWT_CLAIM_VALIDATION_FAILED': console.error(`Claim '${err.claim}' invalid: ${err.reason}`) break case 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED': console.error('Invalid signature') break case 'ERR_JWE_DECRYPTION_FAILED': console.error('Decryption failed') break case 'ERR_JWKS_NO_MATCHING_KEY': console.error('No key matched in JWKS') break case 'ERR_JWKS_MULTIPLE_MATCHING_KEYS': // err[Symbol.asyncIterator] available — iterate matching keys for await (const key of err) { /* try each */ } break case 'ERR_JWKS_TIMEOUT': console.error('Remote JWKS fetch timed out') break case 'ERR_JOSE_ALG_NOT_ALLOWED': console.error('Algorithm not on the allow-list') break case 'ERR_JOSE_NOT_SUPPORTED': console.error('Algorithm/feature not supported in this runtime') break } } ``` --- ## Summary `jose` is the go-to JavaScript library for any application that needs to issue, consume, or inspect JOSE tokens. Its primary use cases span OAuth 2.0 / OpenID Connect authentication (verifying ID tokens with remote JWKS, issuing access tokens, decrypting JWE payloads), API security (signing API responses, verifying client-presented JWTs), and secure data interchange (encrypting arbitrary payloads with JWE for confidential transit). The typed error hierarchy enables resilient authorization middleware that can distinguish expired tokens from tampered ones, missing keys from unsupported algorithms — all at runtime with zero dependencies. Integration with existing stacks is straightforward: import the named exports from `'jose'` (or any subpath like `'jose/jwt/verify'`), generate or import your keys once, and call the async functions directly. The same code runs identically in Node.js (`require(esm)` or `import`), Deno, Bun, Cloudflare Workers, and modern browsers — making `jose` suitable for isomorphic applications, edge functions, and serverless environments where bundle size, runtime compatibility, and standard-compliance are all critical.