What is jwks-rsa?
The jwks-rsa npm package is a library that helps to retrieve RSA signing keys from a JWKS (JSON Web Key Set) endpoint. It is primarily used in scenarios where you need to verify the signature of JSON Web Tokens (JWTs) against public keys published in a JWKS. This is common in modern authentication flows, especially those implementing OpenID Connect.
What are jwks-rsa's main functionalities?
Retrieving RSA signing keys
This feature allows you to retrieve RSA signing keys from a JWKS endpoint. The `getSigningKey` method is used to fetch the key using the `kid` (key ID) from the JWT header. This is useful for verifying JWT signatures.
const jwksClient = require('jwks-rsa');
const client = jwksClient({
jwksUri: 'https://your-domain.com/.well-known/jwks.json'
});
function getKey(header, callback){
client.getSigningKey(header.kid, function(err, key) {
var signingKey = key.publicKey || key.rsaPublicKey;
callback(null, signingKey);
});
}
Integrating with Express.js for JWT authentication
This code snippet demonstrates how to use jwks-rsa with express-jwt middleware for securing Express.js applications. The `expressJwtSecret` method is used to dynamically provide a signing key based on the incoming JWT's `kid`.
const jwt = require('express-jwt');
const jwksRsa = require('jwks-rsa');
const checkJwt = jwt({
secret: jwksRsa.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: 'https://your-domain.com/.well-known/jwks.json'
}),
audience: 'your-audience',
issuer: 'https://your-domain.com/',
algorithms: ['RS256']
});
Other packages similar to jwks-rsa
node-jose
A package for JavaScript Object Signing and Encryption (JOSE) and JSON Web Token (JWT) implementation. It offers similar functionalities for handling JWKS but is more comprehensive in terms of JOSE standards support, including encryption and decryption capabilities, which jwks-rsa does not directly offer.
jsonwebtoken
This package is primarily focused on creating and verifying JSON Web Tokens (JWTs). While it doesn't directly handle JWKS, it is often used in conjunction with libraries like jwks-rsa for verifying JWT signatures against public keys obtained from a JWKS endpoint.
jwks-rsa
A library to retrieve signing keys from a JWKS (JSON Web Key Set) endpoint.
npm install --save jwks-rsa
Usage
You'll provide the client with the JWKS endpoint which exposes your signing keys. Using the getSigningKey
you can then get the signing key that matches a specific kid
.
const jwksClient = require('jwks-rsa');
const client = jwksClient({
jwksUri: 'https://sandrino.auth0.com/.well-known/jwks.json',
requestHeaders: {},
timeout: 30000
});
const kid = 'RkI5MjI5OUY5ODc1N0Q4QzM0OUYzNkVGMTJDOUEzQkFCOTU3NjE2Rg';
const key = await client.getSigningKey(kid);
const signingKey = key.getPublicKey();
Integrations
API
JwksClient Options
jwksUri
: a string that represents the JWKS URItimeout = 30000
: (optional) an integer in miliseconds that controls the request timeoutcache = true
: (optional) enables a LRU Cache (details)rateLimit
: (optional) the default fetcher function (details)fetcher
: (optional) a Promise returning function to fetch data from the JWKS URIrequestHeaders
: (optional) an object of headers to pass to the requestrequestAgent
: (optional) a Node http.Agent
to be passed to the http(s) requestgetKeysInterceptor
: (optional) a promise returning function hook (details)
Caching
By default, signing key verification results are cached in order to prevent excessive HTTP requests to the JWKS endpoint. If a signing key matching the kid
is found, this will be cached and the next time this kid
is requested the signing key will be served from the cache. The caching behavior can be configured as seen below:
const jwksClient = require('jwks-rsa');
const client = jwksClient({
cache: true,
cacheMaxEntries: 5,
cacheMaxAge: 600000,
jwksUri: 'https://sandrino.auth0.com/.well-known/jwks.json'
});
const kid = 'RkI5MjI5OUY5ODc1N0Q4QzM0OUYzNkVGMTJDOUEzQkFCOTU3NjE2Rg';
const key = await client.getSigningKey(kid);
const signingKey = key.getPublicKey();
Rate Limiting
Even if caching is enabled the library will call the JWKS endpoint if the kid
is not available in the cache, because a key rotation could have taken place. To prevent attackers to send many random kid
s you can also configure rate limiting. This will allow you to limit the number of calls that are made to the JWKS endpoint per minute (because it would be highly unlikely that signing keys are rotated multiple times per minute).
const jwksClient = require('jwks-rsa');
const client = jwksClient({
rateLimit: true,
jwksRequestsPerMinute: 10,
jwksUri: 'https://sandrino.auth0.com/.well-known/jwks.json'
});
const kid = 'RkI5MjI5OUY5ODc1N0Q4QzM0OUYzNkVGMTJDOUEzQkFCOTU3NjE2Rg';
const key = await client.getSigningKey(kid);
const signingKey = key.getPublicKey();
Using Request Agent for TLS/SSL Configuration
The requestAgent
property can be used to configure SSL/TLS options. An
example use case is providing a trusted private (i.e. enterprise/corporate) root
certificate authority to establish TLS communication with the jwks_uri
.
const jwksClient = require("jwks-rsa");
const https = require('https');
const client = jwksClient({
jwksUri: 'https://my-enterprise-id-provider/.well-known/jwks.json',
requestHeaders: {},
requestAgent: new https.Agent({
ca: fs.readFileSync(caFile)
})
});
Proxy configuration
You can configure a proxy with using a custom http(s) agent in the requestAgent
option.
Loading keys from local file, environment variable, or other externals
The getKeysInterceptor
property can be used to fetch keys before sending a request to the jwksUri
endpoint. This can be helpful when wanting to load keys from a file, env variable, or an external cache. If a KID cannot be found in the keys returned from the interceptor, it will fallback to the jwksUri
endpoint. This property will continue to work with the provided LRU cache, if the cache is enabled.
const client = new JwksClient({
jwksUri: 'https://my-enterprise-id-provider/.well-known/jwks.json',
getKeysInterceptor: () => {
const file = fs.readFileSync(jwksFile);
return file.keys;
}
});
Running Tests
npm run test
Showing Trace Logs
To show trace logs you can set the following environment variable:
DEBUG=jwks
Output:
jwks Retrieving keys from http://my-authz-server/.well-known/jwks.json +5ms
jwks Keys: +8ms [ { alg: 'RS256',
kty: 'RSA',
use: 'sig',
x5c: [ 'pk1' ],
kid: 'ABC' },
{ alg: 'RS256', kty: 'RSA', use: 'sig', x5c: [], kid: '123' } ]
License
This project is licensed under the MIT license. See the LICENSE file for more info.
[2.0.0] - (2021-03-01)
With version 2 we have added full JWK/JWS support. With this we have bumped the node version to minimum 10. We have also removed Axios and exposed a fetcher
option to allow user's to completely override how the request to the jwksUri
endpoint is made.
Breaking Changes
- Drops support for Node < 10
- No more callbacks, using async/await(promises)
- Removed Axios and changed the API to JwksClient
Changes
Added
Changed
Migration Guide from v1 to v2
Proxies
The proxy option has been removed from the JwksClient. Support for it was a little spotty through Axios, and we wanted to allow users to have more control over the flow. Now you can specify your proxy by overriding the requestAgent
used with an agent with built-in proxy support, or by completely overriding the request library with the fetcher
option.
// OLD
const oldClient = jwksClient({
jwksUri: 'https://sandrino.auth0.com/.well-known/jwks.json',
proxy: 'https://username:pass@address:port'
});
// NEW
const HttpsProxyAgent = require('https-proxy-agent');
const newClient = jwksClient({
jwksUri: 'https://sandrino.auth0.com/.well-known/jwks.json',
requestAgent: new HttpsProxyAgent('https://username:pass@address:port')
});
Request Agent Options
The library no longer gates what http(s) Agent is used, so we have removed requestAgentOptions
and now expose the requestAgent
option when creating a jwksClient
.
// OLD
const oldClient = jwksClient({
jwksUri: 'https://sandrino.auth0.com/.well-known/jwks.json',
requestAgentOptions: {
ca: fs.readFileSync(caFile)
}
});
// NEW
const newClient = jwksClient({
jwksUri: 'https://sandrino.auth0.com/.well-known/jwks.json',
requestAgent: new https.Agent({
ca: fs.readFileSync(caFile)
})
});
Migrated Callbacks to Async/Await
The library no longer supports callbacks. We have migrated to async/await(promises).
// OLD
client.getSigningKey(kid, (err, key) => {
const signingKey = key.getPublicKey();
});
// NEW
const key = await client.getSigningKey(kid);
const signingKey = key.getPublicKey();