Comparing version 2.1.0 to 3.0.0
{ | ||
"name": "get-jwks", | ||
"version": "2.1.0", | ||
"version": "3.0.0", | ||
"description": "Fetch utils for JWKS keys", | ||
@@ -10,3 +10,3 @@ "main": "src/get-jwks.js", | ||
"scripts": { | ||
"test": "tap", | ||
"test": "tap --files=test/**/*.spec.js", | ||
"lint": "eslint src/**/*.js test/**/*.js", | ||
@@ -40,2 +40,6 @@ "ci": "npm run lint && tap --no-color --reporter=spec --coverage-report=json --coverage-report=text --100 test/*.spec.js test/**/*.spec.js" | ||
"eslint-plugin-promise": "^4.3.1", | ||
"fast-jwt": "^1.1.2", | ||
"fastify": "^3.12.0", | ||
"fastify-jwt": "^2.3.0", | ||
"jsonwebtoken": "^8.5.1", | ||
"nock": "^13.0.7", | ||
@@ -42,0 +46,0 @@ "node-cache": "^5.1.2", |
@@ -9,4 +9,2 @@ # get-jwks | ||
Just run: | ||
```bash | ||
@@ -18,2 +16,4 @@ npm install get-jwks | ||
### getJwk | ||
```javascript | ||
@@ -24,3 +24,3 @@ const buildGetJwks = require('get-jwks') | ||
const secret = await getJwks.getSecret({ | ||
const jwk = await getJwks.getJwk({ | ||
domain: 'https://exampe.com/', | ||
@@ -31,19 +31,31 @@ alg: 'token_alg', | ||
// to clear the secret in cache | ||
getJwks.clearCache() | ||
``` | ||
Calling the asynchronous function `getJwk` will fetch the [JSON Web Key](https://tools.ietf.org/html/rfc7517), and verify if any of the public keys matches the `alg` and `kid` values of your JWT token. It will cache the matching key so if called again it will not make another request to retrieve a JWKS. | ||
- `domain`: A string containing the domain (ie: `https://www.example.com/`) from which the library should fetch the JWKS. `get-jwks` will add the JWKS location (`.well-known/jwks.json`) to form the final url (ie: `https://www.example.com/.well-known/jwks.json`). | ||
- `alg`: The alg header parameter represents the cryptographic algorithm used to secure the token. You will find it in your decoded JWT. | ||
- `kid`: The kid is a hint that indicates which key was used to secure the JSON web signature of the token. You will find it in your decoded JWT. | ||
### getPublicKey | ||
### getSecret | ||
```javascript | ||
const buildGetJwks = require('get-jwks') | ||
Calling the `getSecret` will fetch the [JSON Web Key](https://tools.ietf.org/html/rfc7517), Set and verify if any of the public keys matches the `alg` and `kid` values of your JWT token. And it will cache the secret so if called again it will not make another http request to return the secret. It is asynchronous. | ||
const getJwks = buildGetJwks() | ||
- `domain`: A string containing the domain (ie: `https://www.example.com/`) from which the library should fetch the JWKS. `get-jwks` will add the JWKS location (`.well-known/jwks.json`) to form the final url (ie: `https://www.example.com/.well-known/jwks.json`). | ||
- `alg`: The alg header parameter represents the cryptographic algorithm used to secure the token. You will find it in your decoded JWT. | ||
- `kid`: The kid is a hint that indicates which key was used to secure the JSON web signature of the token. You will find it in your decoded JWT. | ||
const publicKey = await getJwks.getPublicKey({ | ||
domain: 'https://exampe.com/', | ||
alg: 'token_alg', | ||
kid: 'token_kid' | ||
}) | ||
``` | ||
Calling the asynchronous function `getPublicKey` will run the `getJwk` function to retrieve a matching key, then convert it to a PEM public key. It requires the same arguments as `getJwk`. | ||
### clearCache | ||
Clears the contents of the cache | ||
```javascript | ||
getJwks.clearCache() | ||
``` | ||
Clears all contents of the cache | ||
@@ -65,2 +77,33 @@ ### Optional cache constuctor | ||
## Integration Examples | ||
### fastify-jwt | ||
[fastify-jwt](https://github.com/fastify/fastify-jwt) is a Json Web Token plugin for [Fastify](https://www.fastify.io/). | ||
The following example includes a scenario where you'd like to varify a JWT against a valid JWK on any request to your Fastify server. Any request with a valid JWT auth token in the header will return a successful response, otherwise will respond with an authentication error. | ||
```javascript | ||
const Fastify = require('fastify') | ||
const fjwt = require('fastify-jwt') | ||
const buildGetJwks = require('get-jwks') | ||
const fastify = Fastify() | ||
const getJwks = buildGetJwks() | ||
fastify.register(fjwt, { | ||
decode: { complete: true }, | ||
secret: (request, token, callback) => { | ||
const { header: { kid, alg }, payload: { iss } } = token | ||
getJwks.getPublicKey({ kid, domain: iss, alg }) | ||
.then(publicKey => callback(null, publicKey), callback) | ||
} | ||
}) | ||
fastify.addHook('onRequest', async (request, reply) => { | ||
await request.jwtVerify() | ||
}) | ||
fastify.listen(3000) | ||
``` | ||
@@ -7,4 +7,4 @@ 'use strict' | ||
const MISSING_KEY_ERROR = 'No matching key found in the set.' | ||
const NO_KEYS_ERROR = 'No keys found in the set.' | ||
const MISSING_JWK_ERROR = 'No matching JWK found in the set.' | ||
const NO_JWKS_ERROR = 'No JWKS found in the response.' | ||
@@ -16,18 +16,18 @@ function buildGetJwks (cacheProps = {}) { | ||
async function getSecret (signatures) { | ||
async function getPublicKey (signatures) { | ||
const key = await getJwk(signatures) | ||
const publicKey = jwkToPem(key) | ||
return publicKey | ||
} | ||
async function getJwk (signatures) { | ||
const { domain, alg, kid } = signatures | ||
const cacheKey = `${alg}:${kid}:${domain}` | ||
const cachedSecret = cache.get(cacheKey) | ||
const cachedJwk = cache.get(cacheKey) | ||
if (cachedSecret) { | ||
return cachedSecret | ||
} else if (cachedSecret === null) { | ||
// null is returned when a previous attempt resulted in the key missing in the JWKs - Do not attemp to fetch again | ||
throw new Error(MISSING_KEY_ERROR) | ||
if (cachedJwk) { | ||
return cachedJwk | ||
} | ||
// ensure there's a trailing slash from the domain | ||
const issuerDomain = domain.endsWith('/') ? domain : `${domain}/` | ||
// Hit the well-known URL in order to get the key | ||
const response = await fetch(`${issuerDomain}.well-known/jwks.json`, { timeout: 5000 }) | ||
@@ -40,3 +40,2 @@ const body = await response.json() | ||
error.body = body | ||
throw error | ||
@@ -46,23 +45,18 @@ } | ||
if (!body.keys || body.keys.length === 0) { | ||
throw new Error(NO_KEYS_ERROR) | ||
throw new Error(NO_JWKS_ERROR) | ||
} | ||
// Find the key with ID and algorithm matching the JWT token header | ||
const key = body.keys.find(k => k.alg === alg && k.kid === kid) | ||
const jwk = body.keys.find(k => k.alg === alg && k.kid === kid) | ||
if (!key) { | ||
// Mark the key as missing | ||
cache.set(cacheKey, null) | ||
throw new Error(MISSING_KEY_ERROR) | ||
if (!jwk) { | ||
throw new Error(MISSING_JWK_ERROR) | ||
} | ||
const secret = jwkToPem(key) | ||
// Save the key in the cache | ||
cache.set(cacheKey, secret) | ||
return secret | ||
cache.set(cacheKey, jwk) | ||
return jwk | ||
} | ||
return { | ||
getSecret, | ||
getPublicKey, | ||
getJwk, | ||
clearCache: () => cache.clear(), | ||
@@ -69,0 +63,0 @@ cache |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
29840
17
438
105
12
1
2