174 lines
3.8 KiB
Markdown
174 lines
3.8 KiB
Markdown
|
# 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
|
||
|
|
||
|
* [x] `jsonwebtoken` (Auth0)
|
||
|
* [x] OIDC (OpenID Connect)
|
||
|
* [x] .well-known/jwks.json (Auth0)
|
||
|
* [x] Other JWKs URLs
|
||
|
|
||
|
Crypto Support
|
||
|
|
||
|
* [x] JWT verification
|
||
|
* [x] RSA (all variants)
|
||
|
* [x] EC / ECDSA (NIST variants P-256, P-384)
|
||
|
* [ ] esoteric variants (excluded to keep the code featherweight and secure)
|
||
|
|
||
|
# Install
|
||
|
|
||
|
```bash
|
||
|
npm install --save keyfetch
|
||
|
```
|
||
|
|
||
|
# Usage
|
||
|
|
||
|
Retrieve a key list of keys:
|
||
|
|
||
|
```js
|
||
|
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:
|
||
|
|
||
|
```js
|
||
|
var keyfetch = require('keyfetch');
|
||
|
var jwt = '...';
|
||
|
|
||
|
keyfetch.verify({ jwt: jwt }).then(function (decoded) {
|
||
|
console.log(decoded);
|
||
|
});
|
||
|
```
|
||
|
|
||
|
Verify a JWT with `jsonwebtoken`:
|
||
|
|
||
|
```js
|
||
|
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:
|
||
|
|
||
|
```js
|
||
|
function isTrustedIssuer(iss) {
|
||
|
return -1 !== [ 'https://partner.com/', 'https://auth0.com/'].indexOf(iss);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
```js
|
||
|
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).
|
||
|
|
||
|
```js
|
||
|
keyfetch.jwks(jwksUrl)
|
||
|
// Promises [ { jwk, thumbprint, pem } ] or fails
|
||
|
```
|
||
|
|
||
|
```js
|
||
|
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.
|
||
|
|
||
|
```js
|
||
|
keyfetch.wellKnownJwks(issuerUrl)
|
||
|
// Promises [ { jwk, thumbprint, pem } ] or fails
|
||
|
```
|
||
|
|
||
|
```js
|
||
|
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.
|
||
|
|
||
|
```js
|
||
|
keyfetch.oidcJwks(issuerUrl)
|
||
|
// Promises [ { jwk, thumbprint, pem } ] or fails
|
||
|
```
|
||
|
|
||
|
```js
|
||
|
keyfetch.oidcJwk(id, issuerUrl)
|
||
|
// Promises { jwk, thumbprint, pem } or fails
|
||
|
```
|
||
|
|
||
|
### Verify JWT
|
||
|
|
||
|
```js
|
||
|
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
|
||
|
|
||
|
```js
|
||
|
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.
|