Comparing version 4.2.0 to 4.3.0
{ | ||
"name": "get-jwks", | ||
"version": "4.2.0", | ||
"version": "4.3.0", | ||
"description": "Fetch utils for JWKS keys", | ||
@@ -10,3 +10,3 @@ "main": "src/get-jwks.js", | ||
"scripts": { | ||
"test": "tap", | ||
"test": "tap test/*.spec.js", | ||
"lint": "eslint ." | ||
@@ -37,9 +37,9 @@ }, | ||
"fastify": "^3.12.0", | ||
"fastify-jwt": "^2.3.0", | ||
"fastify-jwt": "^3.0.0", | ||
"jsonwebtoken": "^8.5.1", | ||
"nock": "^13.0.7", | ||
"prettier": "^2.2.1", | ||
"sinon": "^10.0.0", | ||
"tap": "^14.11.0" | ||
"sinon": "^11.0.0", | ||
"tap": "^15.0.2" | ||
} | ||
} |
@@ -18,2 +18,3 @@ # get-jwks | ||
```js | ||
const https = require('https') | ||
const buildGetJwks = require('get-jwks') | ||
@@ -26,2 +27,5 @@ | ||
providerDiscovery: false, | ||
agent: new https.Agent({ | ||
keepAlive: true, | ||
}), | ||
}) | ||
@@ -33,3 +37,5 @@ ``` | ||
- `allowedDomains`: Array of allowed domains. By default all domains are allowed. | ||
- `providerDiscovery`: Indicates if the Provider Configuration Information is used to automatically get the jwks_uri from the [OpenID Provider Discovery Endpoint](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig). This endpoint is exposing the [Provider Metadata](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata). With this flag set to true the domain will be treated as the OpenID Issuer which is the iss property in the token. Defaults to false | ||
- `providerDiscovery`: Indicates if the Provider Configuration Information is used to automatically get the jwks_uri from the [OpenID Provider Discovery Endpoint](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig). This endpoint is exposing the [Provider Metadata](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata). With this flag set to true the domain will be treated as the OpenID Issuer which is the iss property in the token. Defaults to false. Ignored if jwksPath is specified. | ||
- `jwksPath`: Specify a relative path to the jwks_uri. Example `/otherdir/jwks.json`. Takes precedence over providerDiscovery. Optional. | ||
- `agent`: The custom agent to use for requests, as specified in [node-fetch documentation](https://github.com/node-fetch/node-fetch#custom-agent). Defaults to `null`. | ||
@@ -46,3 +52,3 @@ > `max` and `maxAge` are provided to [lru-cache](https://www.npmjs.com/package/lru-cache). | ||
const jwk = await getJwks.getJwk({ | ||
domain: 'https://exampe.com/', | ||
domain: 'https://example.com/', | ||
alg: 'token_alg', | ||
@@ -120,8 +126,5 @@ kid: 'token_kid', | ||
```js | ||
const { createDecoder, createVerifier } = require('fast-jwt') | ||
const { createVerifier } = require('fast-jwt') | ||
const buildGetJwks = require('get-jwks') | ||
// JWT signed with JWKS | ||
const token = '...' | ||
// well known url of the token issuer | ||
@@ -131,17 +134,17 @@ // often encoded as the `iss` property of the token payload | ||
// complete is necessary to get the header | ||
const decode = createDecoder({ complete: true }) | ||
const getJwks = buildGetJwks({ allowedDomains: [...]}) | ||
// decode the token and extract the header | ||
const { | ||
header: { kid, alg }, | ||
} = decode(token) | ||
// create a verifier function with key as a function | ||
const verifyWithPromise = createVerifier({ | ||
key: async function (token) { | ||
const publicKey = await getJwks.getPublicKey({ | ||
kid: token.kid, | ||
alg: token.alg, | ||
domain, | ||
}) | ||
return publicKey | ||
}, | ||
}) | ||
const getJwks = buildGetJwks() | ||
const publicKey = await getJwks.getPublicKey({ kid, domain, alg }) | ||
const verifyWithPromise = createVerifier({ key: publicKey }) | ||
// verify the token via the public key | ||
const payload = await verifyWithPromise(token) | ||
``` |
@@ -18,23 +18,4 @@ 'use strict' | ||
async function getJwksUri(normalizedDomain) { | ||
const response = await fetch( | ||
`${normalizedDomain}.well-known/openid-configuration`, | ||
{ | ||
timeout: 5000, | ||
} | ||
) | ||
const body = await response.json() | ||
if (!response.ok) { | ||
const error = new Error(response.statusText) | ||
error.response = response | ||
error.body = body | ||
throw error | ||
} | ||
if (!body.jwks_uri) { | ||
throw new Error(errors.NO_JWKS_URI) | ||
} | ||
return body.jwks_uri | ||
function ensureNoLeadingSlash(path) { | ||
return path.startsWith('/') ? path.substring(1) : path | ||
} | ||
@@ -47,3 +28,6 @@ | ||
const providerDiscovery = options.providerDiscovery || false | ||
const jwksPath = options.jwksPath | ||
? ensureNoLeadingSlash(options.jwksPath) | ||
: false | ||
const agent = options.agent || null | ||
const staleCache = new LRU({ max: max * 2, maxAge }) | ||
@@ -56,2 +40,26 @@ const cache = new LRU({ | ||
async function getJwksUri(normalizedDomain) { | ||
const response = await fetch( | ||
`${normalizedDomain}.well-known/openid-configuration`, | ||
{ | ||
agent, | ||
timeout: 5000, | ||
} | ||
) | ||
const body = await response.json() | ||
if (!response.ok) { | ||
const error = new Error(response.statusText) | ||
error.response = response | ||
error.body = body | ||
throw error | ||
} | ||
if (!body.jwks_uri) { | ||
throw new Error(errors.NO_JWKS_URI) | ||
} | ||
return body.jwks_uri | ||
} | ||
async function getPublicKey(signature) { | ||
@@ -97,7 +105,9 @@ return jwkToPem(await this.getJwk(signature)) | ||
async function retrieveJwk(normalizedDomain, alg, kid) { | ||
const jwksUri = providerDiscovery | ||
const jwksUri = jwksPath | ||
? normalizedDomain + jwksPath | ||
: providerDiscovery | ||
? await getJwksUri(normalizedDomain) | ||
: `${normalizedDomain}.well-known/jwks.json` | ||
const response = await fetch(jwksUri, { timeout: 5000 }) | ||
const response = await fetch(jwksUri, { agent, timeout: 5000 }) | ||
const body = await response.json() | ||
@@ -116,3 +126,5 @@ | ||
const jwk = body.keys.find(key => (key.alg === undefined || key.alg === alg) && key.kid === kid) | ||
const jwk = body.keys.find( | ||
key => (key.alg === undefined || key.alg === alg) && key.kid === kid | ||
) | ||
@@ -119,0 +131,0 @@ if (!jwk) { |
@@ -26,3 +26,3 @@ 'use strict' | ||
t.equal(publicKey, jwkToPem(jwk)) | ||
t.deepEqual(jwk, localKey) | ||
t.same(jwk, localKey) | ||
} | ||
@@ -29,0 +29,0 @@ ) |
@@ -5,3 +5,3 @@ 'use strict' | ||
const nock = require('nock') | ||
const { createDecoder, createVerifier } = require('fast-jwt') | ||
const { createVerifier } = require('fast-jwt') | ||
@@ -21,19 +21,19 @@ const { jwks, token } = require('./constants') | ||
t.test('fast-jwt integration tests', async t => { | ||
const myDomain = 'https://localhost/' | ||
nock(myDomain).get('/.well-known/jwks.json').reply(200, jwks) | ||
const domain = 'https://localhost/' | ||
nock(domain).get('/.well-known/jwks.json').reply(200, jwks) | ||
const decodeComplete = createDecoder({ complete: true }) | ||
const sections = decodeComplete(token) | ||
const { | ||
header: { kid, alg }, | ||
} = sections | ||
const getJwks = buildGetJwks() | ||
const publicKey = await getJwks.getPublicKey({ kid, domain: myDomain, alg }) | ||
const verifyWithPromise = createVerifier({ key: publicKey }) | ||
const verifyWithPromise = createVerifier({ | ||
key: async function (token) { | ||
const publicKey = await getJwks.getPublicKey({ | ||
kid: token.kid, | ||
alg: token.alg, | ||
domain, | ||
}) | ||
return publicKey | ||
}, | ||
}) | ||
const payload = await verifyWithPromise(token) | ||
t.strictEqual(payload.name, 'Jane Doe') | ||
t.done() | ||
t.equal(payload.name, 'Jane Doe') | ||
}) |
@@ -55,5 +55,4 @@ 'use strict' | ||
t.strictEqual(response.statusCode, 200) | ||
t.strictEqual(response.body, 'Jane Doe') | ||
t.done() | ||
t.equal(response.statusCode, 200) | ||
t.equal(response.body, 'Jane Doe') | ||
}) | ||
@@ -99,5 +98,4 @@ | ||
t.strictEqual(response.statusCode, 200) | ||
t.strictEqual(response.body, 'Jane Doe') | ||
t.done() | ||
t.equal(response.statusCode, 200) | ||
t.equal(response.body, 'Jane Doe') | ||
}) |
@@ -49,5 +49,20 @@ 'use strict' | ||
t.ok(jwk) | ||
t.deepEqual(jwk, key) | ||
t.same(jwk, key) | ||
}) | ||
t.test('returns a jwk if alg and kid match and path is specified', async t => { | ||
nock(domain).get('/otherdir/jwks.json').reply(200, jwks) | ||
const getJwks = buildGetJwks({ jwksPath: '/otherdir/jwks.json' }) | ||
const key = jwks.keys[0] | ||
const jwk = await getJwks.getJwk({ | ||
domain, | ||
alg: key.alg, | ||
kid: key.kid, | ||
}) | ||
t.ok(jwk) | ||
t.same(jwk, key) | ||
}) | ||
t.test('returns a jwk if no alg is provided and kid match', async t => { | ||
@@ -61,3 +76,3 @@ nock(domain).get('/.well-known/jwks.json').reply(200, jwks) | ||
t.ok(jwk) | ||
t.deepEqual(jwk, key) | ||
t.same(jwk, key) | ||
}) | ||
@@ -76,3 +91,3 @@ | ||
t.ok(jwk) | ||
t.deepEqual(jwk, key) | ||
t.same(jwk, key) | ||
}) | ||
@@ -122,2 +137,11 @@ | ||
t.test('supports path without leading slash', async t => { | ||
nock(domain).get('/otherdir/jwks.json').reply(200, jwks) | ||
const getJwks = buildGetJwks({ jwksPath: 'otherdir/jwks.json' }) | ||
const [{ alg, kid }] = jwks.keys | ||
const key = await getJwks.getJwk({ domain: 'https://localhost', alg, kid }) | ||
t.ok(key) | ||
}) | ||
t.test('does not execute concurrent requests', () => { | ||
@@ -166,3 +190,3 @@ nock(domain).get('/.well-known/jwks.json').once().reply(200, jwks) | ||
t.sameStrict(key, key1) | ||
t.strictSame(key, key1) | ||
}) | ||
@@ -169,0 +193,0 @@ |
@@ -54,3 +54,3 @@ 'use strict' | ||
t.ok(jwk) | ||
t.deepEqual(jwk, key) | ||
t.same(jwk, key) | ||
}) | ||
@@ -67,3 +67,3 @@ | ||
t.ok(jwk) | ||
t.deepEqual(jwk, key) | ||
t.same(jwk, key) | ||
}) | ||
@@ -83,3 +83,3 @@ | ||
t.ok(jwk) | ||
t.deepEqual(jwk, key) | ||
t.same(jwk, key) | ||
}) | ||
@@ -192,3 +192,3 @@ | ||
t.sameStrict(key, key1) | ||
t.strictSame(key, key1) | ||
} | ||
@@ -195,0 +195,0 @@ ) |
Sorry, the diff of this file is not supported yet
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
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
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
44893
23
823
145
3