An easy, lightweight, and secure module for fetching JWK Public Keys and verifying JWTs. Great for OIDC, Auth0, JWKs URLs.
Go to file
AJ ONeal 2c36227afd v1.1.6: bugfix verify and ecdsa keys 2019-03-08 18:00:41 -07:00
README.md initial commit 2019-02-25 15:54:08 -07:00
keyfetch-test.js v1.1.4: fix fetching bug 2019-02-25 16:17:26 -07:00
keyfetch.js v1.1.6: bugfix verify and ecdsa keys 2019-03-08 18:00:41 -07:00
package-lock.json initial commit 2019-02-25 15:54:08 -07:00
package.json v1.1.6: bugfix verify and ecdsa keys 2019-03-08 18:00:41 -07:00

README.md

keyfetch

Lightweight support for fetching JWKs.

Fetches JSON native JWKs and exposes them as PEMs that can be consumed by the jsonwebtoken package (and node's native RSA and ECDSA crypto APIs).

Features

Works great for

  • jsonwebtoken (Auth0)
  • OIDC (OpenID Connect)
  • .well-known/jwks.json (Auth0)
  • Other JWKs URLs

Crypto Support

  • JWT verification
  • RSA (all variants)
  • EC / ECDSA (NIST variants P-256, P-384)
  • esoteric variants (excluded to keep the code featherweight and secure)

Install

npm install --save keyfetch

Usage

Retrieve a key list of keys:

var keyfetch = require('keyfetch');

keyfetch.oidcJwks("https://example.com/").then(function (results) {
  results.forEach(function (result) {
    console.log(result.jwk);
    console.log(result.thumprint);
    console.log(result.pem);
  });
});

Quick JWT verification:

var keyfetch = require('keyfetch');
var jwt = '...';

keyfetch.verify({ jwt: jwt }).then(function (decoded) {
  console.log(decoded);
});

Verify a JWT with jsonwebtoken:

var keyfetch = require('keyfetch');
var jwt = require('jsonwebtoken');
var auth = "..."; // some JWT
var token = jwt.decode(auth, { json: true, complete: true })

if (!isTrustedIssuer(token.payload.iss)) {
  throw new Error("untrusted issuer");
}

keyfetch.oidcJwk(
  token.header.kid
, token.payload.iss
).then(function (result) {
  console.log(result.jwk);
  console.log(result.thumprint);
  console.log(result.pem);

  jwt.verify(jwt, pem);
});

Note: You might implement isTrustedIssuer one of these:

function isTrustedIssuer(iss) {
  return -1 !== [ 'https://partner.com/', 'https://auth0.com/'].indexOf(iss);
}
function isTrustedIssuer(iss) {
  return /^https:/.test(iss) &&         // must be a secure domain
    /(\.|^)example\.com$/.test(iss);    // can be example.com or any subdomain
}

API

All API calls will return the RFC standard JWK SHA256 thumbprint as well as a PEM version of the key.

Note: When specifying id, it may be either kid (as in token.header.kid) or thumbprint (as in result.thumbprint).

JWKs URLs

Retrieves keys from a URL such as https://example.com/jwks/ with the format { keys: [ { kid, kty, exp, ... } ] } and returns the array of keys (as well as thumbprint and jwk-to-pem).

keyfetch.jwks(jwksUrl)
// Promises [ { jwk, thumbprint, pem } ] or fails
keyfetch.jwk(id, jwksUrl)
// Promises { jwk, thumbprint, pem } or fails

Auth0

If https://example.com/ is used as issuerUrl it will resolve to https://example.com/.well-known/jwks.json and return the keys.

keyfetch.wellKnownJwks(issuerUrl)
// Promises [ { jwk, thumbprint, pem } ] or fails
keyfetch.wellKnownJwk(id, issuerUrl)
// Promises { jwk, thumbprint, pem } or fails

OIDC

If https://example.com/ is used as issuerUrl then it will first resolve to https://example.com/.well-known/openid-configuration and then follow jwks_uri to return the keys.

keyfetch.oidcJwks(issuerUrl)
// Promises [ { jwk, thumbprint, pem } ] or fails
keyfetch.oidcJwk(id, issuerUrl)
// Promises { jwk, thumbprint, pem } or fails

Verify JWT

keyfetch.verify({ jwt: jwk, strategy: 'oidc' })
// Promises a decoded JWT { headers, payload, signature } or fails
  • strategy may be oidc (default) , auth0, or a direct JWKs url.

Cache Settings

keyfetch.init({
  // set all keys at least 1 hour (regardless of jwk.exp)
  mincache: 1 * 60 * 60

  // expire each key after 3 days (regardless of jwk.exp)
, maxcache: 3 * 24 * 60 * 60

  // re-fetch a key up to 15 minutes before it expires (only if used)
, staletime: 15 * 60
})

There is no background task to cleanup expired keys as of yet. For now you can limit the number of keys fetched by having a simple whitelist.