initial commit
This commit is contained in:
		
						commit
						6c071af9cd
					
				
							
								
								
									
										173
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,173 @@
 | 
			
		||||
# 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.
 | 
			
		||||
							
								
								
									
										20
									
								
								keyfetch-test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								keyfetch-test.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
var keyfetch = require('./keyfetch.js');
 | 
			
		||||
 | 
			
		||||
keyfetch.init({});
 | 
			
		||||
keyfetch.oidcJwks("https://bigsquid.auth0.com").then(function (jwks) {
 | 
			
		||||
  console.log(jwks);
 | 
			
		||||
  return keyfetch.oidcJwk(jwks[0].thumbprint, "https://bigsquid.auth0.com").then(function (jwk) {
 | 
			
		||||
    console.log(jwk);
 | 
			
		||||
  });
 | 
			
		||||
}).catch(function (err) {
 | 
			
		||||
  console.error(err);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
var jwt = '...';
 | 
			
		||||
keyfetch.verify({ jwt: jwt }).catch(function (err) {
 | 
			
		||||
  console.log(err);
 | 
			
		||||
});
 | 
			
		||||
*/
 | 
			
		||||
							
								
								
									
										301
									
								
								keyfetch.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										301
									
								
								keyfetch.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,301 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
var keyfetch = module.exports;
 | 
			
		||||
 | 
			
		||||
var promisify = require('util').promisify;
 | 
			
		||||
var requestAsync = promisify(require('@coolaj86/urequest'));
 | 
			
		||||
var Rasha = require('rasha');
 | 
			
		||||
var Eckles = require('eckles');
 | 
			
		||||
var mincache = 1 * 60 * 60;
 | 
			
		||||
var maxcache = 3 * 24 * 60 * 60;
 | 
			
		||||
var staletime = 15 * 60;
 | 
			
		||||
var keyCache = {};
 | 
			
		||||
 | 
			
		||||
/*global Promise*/
 | 
			
		||||
function checkMinDefaultMax(opts, key, n, d, x) {
 | 
			
		||||
  var i = opts[key];
 | 
			
		||||
  if (!i && 0 !== i) { return d; }
 | 
			
		||||
  if (i >= n && i >= x) {
 | 
			
		||||
    return parseInt(i, 10);
 | 
			
		||||
  } else {
 | 
			
		||||
    throw new Error("opts." + key + " should be at least " + n + " and at most " + x + ", not " + i);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
keyfetch.init = function (opts) {
 | 
			
		||||
  mincache = checkMinDefaultMax(opts, 'mincache',
 | 
			
		||||
    1 * 60,
 | 
			
		||||
    mincache,
 | 
			
		||||
    31 * 24 * 60 * 60
 | 
			
		||||
  );
 | 
			
		||||
  maxcache = checkMinDefaultMax(opts, 'maxcache',
 | 
			
		||||
    1 * 60 * 60,
 | 
			
		||||
    maxcache,
 | 
			
		||||
    31 * 24 * 60 * 60
 | 
			
		||||
  );
 | 
			
		||||
  staletime = checkMinDefaultMax(opts, 'staletime',
 | 
			
		||||
    1 * 60,
 | 
			
		||||
    staletime,
 | 
			
		||||
    31 * 24 * 60 * 60
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
keyfetch._oidc = function (iss) {
 | 
			
		||||
  return Promise.resolve().then(function () {
 | 
			
		||||
    return requestAsync({
 | 
			
		||||
      url: normalizeIss(iss) + '/.well-known/openid-configuration'
 | 
			
		||||
    , json: true
 | 
			
		||||
    }).then(function (resp) {
 | 
			
		||||
      var oidcConf = resp.body;
 | 
			
		||||
      if (!oidcConf.jwks_uri) {
 | 
			
		||||
        throw new Error("Failed to retrieve openid configuration");
 | 
			
		||||
      }
 | 
			
		||||
      return oidcConf;
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
keyfetch._wellKnownJwks = function (iss) {
 | 
			
		||||
  return Promise.resolve().then(function () {
 | 
			
		||||
    return keyfetch._jwks(normalizeIss(iss) + '/.well-known/jwks.json');
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
keyfetch._jwks = function (iss) {
 | 
			
		||||
  return requestAsync({ url: iss, json: true }).then(function (resp) {
 | 
			
		||||
    return Promise.all(resp.body.keys.map(function (jwk) {
 | 
			
		||||
      // EC keys have an x values, whereas RSA keys do not
 | 
			
		||||
      var Keypairs = jwk.x ? Eckles : Rasha;
 | 
			
		||||
      return Keypairs.thumbprint({ jwk: jwk }).then(function (thumbprint) {
 | 
			
		||||
        return Keypairs.export({ jwk: jwk }).then(function (pem) {
 | 
			
		||||
          var cacheable = {
 | 
			
		||||
            jwk: jwk
 | 
			
		||||
          , thumbprint: thumbprint
 | 
			
		||||
          , pem: pem
 | 
			
		||||
          };
 | 
			
		||||
          return cacheable;
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    }));
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
keyfetch.jwks = function (jwkUrl) {
 | 
			
		||||
  // TODO DRY up a bit
 | 
			
		||||
  return keyfetch._jwks(jwkUrl).then(function (results) {
 | 
			
		||||
    return Promise.all(results.map(function (result) {
 | 
			
		||||
      return keyfetch._setCache(result.jwk.iss || jwkUrl, result);
 | 
			
		||||
    })).then(function () {
 | 
			
		||||
      // cacheable -> hit (keep original externally immutable)
 | 
			
		||||
      return JSON.parse(JSON.stringify(results));
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
keyfetch.wellKnownJwks = function (iss) {
 | 
			
		||||
  // TODO DRY up a bit
 | 
			
		||||
  return keyfetch._wellKnownJwks(iss).then(function (results) {
 | 
			
		||||
    return Promise.all(results.map(function (result) {
 | 
			
		||||
      return keyfetch._setCache(result.jwk.iss || iss, result);
 | 
			
		||||
    })).then(function () {
 | 
			
		||||
      // result -> hit (keep original externally immutable)
 | 
			
		||||
      return JSON.parse(JSON.stringify(results));
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
keyfetch.oidcJwks = function (iss) {
 | 
			
		||||
  return keyfetch._oidc(iss).then(function (oidcConf) {
 | 
			
		||||
    // TODO DRY up a bit
 | 
			
		||||
    return keyfetch._jwks(oidcConf.jwks_uri).then(function (results) {
 | 
			
		||||
      return Promise.all(results.map(function (result) {
 | 
			
		||||
        return keyfetch._setCache(result.jwk.iss || iss, result);
 | 
			
		||||
      })).then(function () {
 | 
			
		||||
        // result -> hit (keep original externally immutable)
 | 
			
		||||
        return JSON.parse(JSON.stringify(results));
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
keyfetch.oidcJwk = function (id, iss) {
 | 
			
		||||
  // TODO [2] DRY this up a bit
 | 
			
		||||
  return keyfetch._checkCache(id, iss).then(function (hit) {
 | 
			
		||||
    if (hit) {
 | 
			
		||||
      return Promise.resolve(hit);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return keyfetch.oidcJwks(iss).then(function (results) {
 | 
			
		||||
      var result = results.some(function (result) {
 | 
			
		||||
        // we already checked iss above
 | 
			
		||||
        return result.jwk.kid === id || result.thumbprint === id;
 | 
			
		||||
      })[0];
 | 
			
		||||
 | 
			
		||||
      if (!result) {
 | 
			
		||||
        throw new Error("No JWK found by kid or thumbprint '" + id + "'");
 | 
			
		||||
      }
 | 
			
		||||
      return result;
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
keyfetch.wellKnownJwk = function (id, iss) {
 | 
			
		||||
  // TODO [2] DRY this up a bit
 | 
			
		||||
  return keyfetch._checkCache(id, iss).then(function (hit) {
 | 
			
		||||
    if (hit) {
 | 
			
		||||
      return Promise.resolve(hit);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return keyfetch.wellKnownJwks(iss).then(function (results) {
 | 
			
		||||
      var result = results.some(function (result) {
 | 
			
		||||
        // we already checked iss above
 | 
			
		||||
        return result.jwk.kid === id || result.thumbprint === id;
 | 
			
		||||
      })[0];
 | 
			
		||||
 | 
			
		||||
      if (!result) {
 | 
			
		||||
        throw new Error("No JWK found by kid or thumbprint '" + id + "'");
 | 
			
		||||
      }
 | 
			
		||||
      return result;
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
keyfetch.jwk = function (id, jwksUrl) {
 | 
			
		||||
  // TODO [2] DRY this up a bit
 | 
			
		||||
  return keyfetch._checkCache(id, jwksUrl).then(function (hit) {
 | 
			
		||||
    if (hit) {
 | 
			
		||||
      return Promise.resolve(hit);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return keyfetch.jwks(jwksUrl).then(function (results) {
 | 
			
		||||
      var result = results.some(function (result) {
 | 
			
		||||
        // we already checked iss above
 | 
			
		||||
        return result.jwk.kid === id || result.thumbprint === id;
 | 
			
		||||
      })[0];
 | 
			
		||||
 | 
			
		||||
      if (!result) {
 | 
			
		||||
        throw new Error("No JWK found by kid or thumbprint '" + id + "'");
 | 
			
		||||
      }
 | 
			
		||||
      return result;
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
keyfetch._checkCache = function (id, iss) {
 | 
			
		||||
  return Promise.resolve().then(function () {
 | 
			
		||||
    // We cache by thumbprint and (kid + '@' + iss),
 | 
			
		||||
    // so it's safe to check without appending the issuer
 | 
			
		||||
    var hit = keyCache[id];
 | 
			
		||||
    if (!hit) {
 | 
			
		||||
      hit = keyCache[id + '@' + normalizeIss(iss)];
 | 
			
		||||
    }
 | 
			
		||||
    if (!hit) {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var now = Math.round(Date.now() / 1000);
 | 
			
		||||
    var left = hit.expiresAt - now;
 | 
			
		||||
    // not guarding number checks since we know that we
 | 
			
		||||
    // set 'now' and 'expiresAt' correctly elsewhere
 | 
			
		||||
    if (left > staletime) {
 | 
			
		||||
      return JSON.parse(JSON.stringify(hit));
 | 
			
		||||
    }
 | 
			
		||||
    if (left > 0) {
 | 
			
		||||
      return JSON.parse(JSON.stringify(hit));
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
keyfetch._setCache = function (iss, cacheable) {
 | 
			
		||||
  // force into a number
 | 
			
		||||
  var expiresAt = parseInt(cacheable.jwk.exp, 10) || 0;
 | 
			
		||||
  var now = Date.now() / 1000;
 | 
			
		||||
  var left = expiresAt - now;
 | 
			
		||||
 | 
			
		||||
  // TODO maybe log out when any of these non-ideal cases happen?
 | 
			
		||||
  if (!left) {
 | 
			
		||||
    expiresAt = now + maxcache;
 | 
			
		||||
  } else if (left < mincache) {
 | 
			
		||||
    expiresAt = now + mincache;
 | 
			
		||||
  } else if (left > maxcache) {
 | 
			
		||||
    expiresAt = now + maxcache;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // cacheable = { jwk, thumprint, pem }
 | 
			
		||||
  cacheable.createdAt = now;
 | 
			
		||||
  cacheable.expiresAt = expiresAt;
 | 
			
		||||
  keyCache[cacheable.thumbprint] = cacheable;
 | 
			
		||||
  keyCache[cacheable.jwk.kid + '@' + normalizeIss(iss)] = cacheable;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function normalizeIss(iss) {
 | 
			
		||||
  // We definitely don't want false negatives stemming
 | 
			
		||||
  // from https://example.com vs https://example.com/
 | 
			
		||||
  // We also don't want to allow insecure issuers
 | 
			
		||||
  if (/^http:/.test(iss) && !process.env.KEYFETCH_ALLOW_INSECURE_HTTP) {
 | 
			
		||||
    // note, we wrap some things in promises just so we can throw here
 | 
			
		||||
    throw new Error("'" + iss + "' is NOT secure. Set env 'KEYFETCH_ALLOW_INSECURE_HTTP=true' to allow for testing.");
 | 
			
		||||
  }
 | 
			
		||||
  return iss.replace(/\/$/, '');
 | 
			
		||||
}
 | 
			
		||||
keyfetch._decode = function (jwt) {
 | 
			
		||||
  var parts = jwt.split('.');
 | 
			
		||||
  return {
 | 
			
		||||
    header: JSON.parse(Buffer.from(parts[0], 'base64'))
 | 
			
		||||
  , payload: JSON.parse(Buffer.from(parts[1], 'base64'))
 | 
			
		||||
  , signature: parts[2] //Buffer.from(parts[2], 'base64')
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
keyfetch.verify = function (opts) {
 | 
			
		||||
  var jwt = opts.jwt;
 | 
			
		||||
  return Promise.resolve().then(function () {
 | 
			
		||||
    var decoded;
 | 
			
		||||
    var exp;
 | 
			
		||||
    var nbf;
 | 
			
		||||
    var valid;
 | 
			
		||||
    try {
 | 
			
		||||
      decoded = keyfetch._decode(jwt);
 | 
			
		||||
      exp = decoded.payload.exp;
 | 
			
		||||
      nbf = decoded.payload.nbf;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      throw new Error("could not parse opts.jwt: '" + jwt + "'");
 | 
			
		||||
    }
 | 
			
		||||
    if (exp) {
 | 
			
		||||
      valid = (parseInt(exp, 10) - (Date.now()/1000) > 0);
 | 
			
		||||
      if (!valid) {
 | 
			
		||||
        throw new Error("token's 'exp' has passed or could not parsed: '" + exp + "'");
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (nbf) {
 | 
			
		||||
      valid = (parseInt(nbf, 10) - (Date.now()/1000) <= 0);
 | 
			
		||||
      if (!valid) {
 | 
			
		||||
        throw new Error("token's 'nbf' has not been reached or could not parsed: '" + nbf + "'");
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    var kid = decoded.header.kid;
 | 
			
		||||
    var iss;
 | 
			
		||||
    var fetcher;
 | 
			
		||||
    if (!opts.strategy || 'oidc' === opts.strategy) {
 | 
			
		||||
      iss = decoded.payload.iss;
 | 
			
		||||
      fetcher = keyfetch.oidcJwks;
 | 
			
		||||
    } else if ('auth0' === opts.strategy || 'well-known' === opts.strategy) {
 | 
			
		||||
      iss = decoded.payload.iss;
 | 
			
		||||
      fetcher = keyfetch.wellKnownJwks;
 | 
			
		||||
    } else {
 | 
			
		||||
      iss = opts.strategy;
 | 
			
		||||
      fetcher = keyfetch.jwks;
 | 
			
		||||
    }
 | 
			
		||||
    function verify(jwk, payload) {
 | 
			
		||||
      var alg = 'RSA-SHA' + decoded.header.alg.replace(/[^\d]+/i, '');
 | 
			
		||||
      return require('crypto')
 | 
			
		||||
        .createVerify(alg)
 | 
			
		||||
        .update(jwt.split('.')[0] + '.' + payload)
 | 
			
		||||
        .verify(jwk.pem, decoded.signature, 'base64');
 | 
			
		||||
    }
 | 
			
		||||
    return fetcher(iss).then(function (jwks) {
 | 
			
		||||
      var payload = jwt.split('.')[1]; // as string, as it was signed
 | 
			
		||||
      if (jwks.some(function (jwk) {
 | 
			
		||||
        if (kid) {
 | 
			
		||||
          if (kid !== jwk.kid && kid !== jwk.thumbprint) { return; }
 | 
			
		||||
          if (verify(jwk, payload)) { return true; }
 | 
			
		||||
          throw new Error('token signature verification was unsuccessful');
 | 
			
		||||
        } else {
 | 
			
		||||
          if (verify(jwk, payload)) { return true; }
 | 
			
		||||
        }
 | 
			
		||||
      })) {
 | 
			
		||||
        return decoded;
 | 
			
		||||
      }
 | 
			
		||||
      throw new Error("Retrieved a list of keys, but none of them matched the 'kid' (key id) of the token.");
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										23
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "keyfetch",
 | 
			
		||||
  "version": "1.1.0",
 | 
			
		||||
  "lockfileVersion": 1,
 | 
			
		||||
  "requires": true,
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@coolaj86/urequest": {
 | 
			
		||||
      "version": "1.3.6",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@coolaj86/urequest/-/urequest-1.3.6.tgz",
 | 
			
		||||
      "integrity": "sha512-9rBXLFSb5D19opGeXdD/WuiFJsA4Pk2r8VUGEAeUZUxB1a2zB47K85BKAx3Gy9i4nZwg22ejlJA+q9DVrpQlbA=="
 | 
			
		||||
    },
 | 
			
		||||
    "eckles": {
 | 
			
		||||
      "version": "1.4.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/eckles/-/eckles-1.4.0.tgz",
 | 
			
		||||
      "integrity": "sha512-Bm5dpwhsBuoCHvKCY3gAvP8XFyXH7im8uAu3szykpVNbFBdC+lOuV8vLC8fvTYRZBfFqB+k/P6ud/ZPVO2V2tA=="
 | 
			
		||||
    },
 | 
			
		||||
    "rasha": {
 | 
			
		||||
      "version": "1.2.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/rasha/-/rasha-1.2.1.tgz",
 | 
			
		||||
      "integrity": "sha512-cs4Hu/rVF3/Qucq+V7lxSz449VfHNMVXJaeajAHno9H5FC1PWlmS4NM6IAX5jPKFF0IC2rOdHdf7iNxQuIWZag=="
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
			
		||||
{ "author": {
 | 
			
		||||
    "name": "AJ ONeal",
 | 
			
		||||
    "email": "solderjs@gmail.com"
 | 
			
		||||
  },
 | 
			
		||||
  "bundleDependencies": false,
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@coolaj86/urequest": "^1.3.6",
 | 
			
		||||
    "eckles": "^1.4.0",
 | 
			
		||||
    "rasha": "^1.2.1"
 | 
			
		||||
  },
 | 
			
		||||
  "deprecated": false,
 | 
			
		||||
  "description": "Lightweight support for fetching JWKs.",
 | 
			
		||||
  "files": [
 | 
			
		||||
    "keyfetch-test.js"
 | 
			
		||||
  ],
 | 
			
		||||
  "keywords": [
 | 
			
		||||
    "jwks",
 | 
			
		||||
    "jwk",
 | 
			
		||||
    "jwt",
 | 
			
		||||
    "auth0",
 | 
			
		||||
    "pem",
 | 
			
		||||
    "RSA",
 | 
			
		||||
    "EC",
 | 
			
		||||
    "ECDSA",
 | 
			
		||||
    "OIDC",
 | 
			
		||||
    "well-known"
 | 
			
		||||
  ],
 | 
			
		||||
  "license": "MPL-2.0",
 | 
			
		||||
  "main": "keyfetch.js",
 | 
			
		||||
  "name": "keyfetch",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "test": "echo \"Error: no test specified\" && exit 1"
 | 
			
		||||
  },
 | 
			
		||||
  "version": "1.1.1"
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user