oidc-provider allows to be extended and configured in various ways to fit a variety of use cases. You will have to configure your instance with how to find your user accounts, where to store and retrieve persisted data from and where your end-user interactions happen. The example application is a good starting point to get an idea of what you should provide.
If you want to quickly add OpenID Connect authentication to Node.js apps, feel free to check out Auth0's Node.js SDK and free plan at auth0.com/overview.
If you or your business use oidc-provider, please consider becoming a Patron so I can continue maintaining it and adding new features carefree. You may also donate one-time via PayPal.
Table of Contents
- Basic configuration example
- Default configuration values
- Accounts
- User flows
- Custom Grant Types
- Registering module middlewares (helmet, ip-filters, rate-limiters, etc)
- Pre- and post-middlewares
- Mounting oidc-provider
- Trusting TLS offloading proxies
- Configuration options
- adapter
- clients
- findAccount
- jwks
- features
- backchannelLogout
- certificateBoundAccessTokens
- claimsParameter
- clientCredentials
- devInteractions
- deviceFlow
- encryption
- frontchannelLogout
- introspection
- jwtIntrospection
- jwtResponseModes
- registration
- registrationManagement
- request
- requestUri
- resourceIndicators
- revocation
- sessionManagement
- webMessageResponseMode
- acrValues
- audiences
- claims
- clientBasedCORS
- clientDefaults
- clockTolerance
- conformIdTokenClaims
- cookies
- discovery
- dynamicScopes
- expiresWithSession
- extraClientMetadata
- extraParams
- formats
- httpOptions
- interactions
- interactionUrl
- introspectionEndpointAuthMethods
- issueRefreshToken
- logoutSource
- pairwiseIdentifier
- pkceMethods
- postLogoutSuccessSource
- renderError
- responseTypes
- revocationEndpointAuthMethods
- rotateRefreshToken
- routes
- scopes
- subjectTypes
- tokenEndpointAuthMethods
- ttl
- whitelistedJWA
const Provider = require('oidc-provider');
const configuration = {
// ... see the available options in Configuration options section
features: {
introspection: { enabled: true },
revocation: { enabled: true },
},
formats: {
AccessToken: 'jwt',
},
clients: [{
client_id: 'foo',
client_secret: 'bar',
redirect_uris: ['http://lvh.me:8080/cb'],
// + other client properties
}],
// ...
};
const oidc = new Provider('http://localhost:3000', configuration);
// express/nodejs style application callback (req, res, next) for use with express apps, see /examples/express.js
oidc.callback
// koa application for use with koa apps, see /examples/koa.js
oidc.app
// or just expose a server standalone, see /examples/standalone.js
const server = oidc.listen(3000, () => {
console.log('oidc-provider listening on port 3000, check http://localhost:3000/.well-known/openid-configuration');
});
Default values are available for all configuration options. Available in code as well as in this document.
oidc-provider needs to be able to find an account and once found the account needs to have an
accountId
property as well as claims()
function returning an object with claims that correspond
to the claims your issuer supports. Tell oidc-provider how to find your account by an ID.
#claims()
can also return a Promise later resolved / rejected.
const oidc = new Provider('http://localhost:3000', {
async findAccount(ctx, id) {
return {
accountId: id,
async claims(use, scope) { return { sub: id }; },
};
}
});
Since oidc-provider only comes with feature-less views and interaction handlers it is up to you to fill those in, here is how oidc-provider allows you to do so:
When oidc-provider cannot fulfill the authorization request for any of the possible reasons (missing
user session, requested ACR not fulfilled, prompt requested, ...) it will resolve an interactionUrl
(configurable) and redirect the User-Agent to that url. Before doing so it will save a short-lived
session and dump its identifier into a cookie scoped to the resolved interaction path.
This session contains:
- details of the interaction that is required
- all authorization request parameters
- current session account ID should there be one
- the uid of the authorization request
- the url to redirect the user to once interaction is finished
oidc-provider expects that you resolve all future interactions in one go and only then redirect the User-Agent back with the results
Once the required interactions are finished you are expected to redirect back to the authorization endpoint, affixed by the uid of the original request and the interaction results stored in the interaction session object.
The Provider instance comes with helpers that aid with getting interaction details as well as packing the results. See them used in the step-by-step or in-repo examples.
#provider.interactionDetails(req)
// with express
expressApp.get('/interaction/:uid', async (req, res) => {
const details = await provider.interactionDetails(req);
// ...
});
// with koa
router.get('/interaction/:uid', async (ctx, next) => {
const details = await provider.interactionDetails(ctx.req);
// ...
});
#provider.interactionFinished(req, res, result)
// with express
expressApp.post('/interaction/:uid/login', async (req, res) => {
return provider.interactionFinished(req, res, result); // result object below
});
// with koa
router.post('/interaction/:uid', async (ctx, next) => {
return provider.interactionFinished(ctx.req, ctx.res, result); // result object below
});
// result should be an object with some or all the following properties
{
// authentication/login prompt got resolved, omit if no authentication happened, i.e. the user
// cancelled
login: {
account: '7ff1d19a-d3fd-4863-978e-8cce75fa880c', // logged-in account id
acr: string, // acr value for the authentication
remember: boolean, // true if provider should use a persistent cookie rather than a session one, defaults to true
ts: number, // unix timestamp of the authentication, defaults to now()
},
// consent was given by the user to the client for this session
consent: {
rejectedScopes: [], // array of strings, scope names the end-user has not granted
rejectedClaims: [], // array of strings, claim names the end-user has not granted
},
// meta is a free object you may store alongside an authorization. It can be useful
// during the interaction check to verify information on the ongoing session.
meta: {
// object structure up-to-you
},
['custom prompt name resolved']: {},
}
// optionally, interactions can be primaturely exited with a an error by providing a result
// object as follow:
{
// an error field used as error code indicating a failure during the interaction
error: 'access_denied',
// an optional description for this error
error_description: 'Insufficient permissions: scope out of reach for this Account',
}
#provider.interactionResult
Unlike #provider.interactionFinished
authorization request resume uri is returned instead of
immediate http redirect. It should be used when custom response handling is needed e.g. making AJAX
login where redirect information is expected to be available in the response.
// with express
expressApp.post('/interaction/:uid/login', async (req, res) => {
const redirectTo = await provider.interactionResult(req, res, result);
res.send({ redirectTo });
});
// with koa
router.post('/interaction/:uid', async (ctx, next) => {
const redirectTo = await provider.interactionResult(ctx.req, ctx.res, result);
ctx.body = { redirectTo };
});
#provider.setProviderSession
Sometimes interactions need to be interrupted before finishing and need to be picked up later,
or a session just needs to be established from outside the regular authorization request.
#provider.setProviderSession
will take care of setting the proper cookies and storing the
updated/created session object.
Signature:
async setProviderSession(req, res, {
account, // account id string
ts = epochTime(), // [optional] login timestamp, defaults to current timestamp
remember = true, // [optional] set the session as persistent, defaults to true
clients = [], // [optional] array of client id strings to pre-authorize in the updated session
meta: { // [optional] object with keys being client_ids present in clients with their respective meta
[client_id]: {},
}
} = {})
// with express
expressApp.post('/interaction/:uid/login', async (req, res) => {
await provider.setProviderSession(req, res, { account: 'accountId' });
// ...
});
// with koa
router.post('/interaction/:uid/login', async (ctx, next) => {
await provider.setProviderSession(ctx.req, ctx.res, { account: 'accountId' });
// ...
});
oidc-provider comes with the basic grants implemented, but you can register your own grant types, for example to implement an OAuth 2.0 Token Exchange. You can check the standard grant factories here.
Note: Since custom grant types are registered after instantiating a Provider instance they can only be used by clients loaded by an adapter, statically configured clients will throw InvalidClientMetadata errors.
const parameters = [
'audience', 'resource', 'scope', 'requested_token_type',
'subject_token', 'subject_token_type',
'actor_token', 'actor_token_type'
];
const allowedDuplicateParameters = ['audience', 'resource'];
const grantType = 'urn:ietf:params:oauth:grant-type:token-exchange';
async function tokenExchangeHandler(ctx, next) {
// ctx.oidc.params holds the parsed parameters
// ctx.oidc.client has the authenticated client
// your grant implementation
// see /lib/actions/grants for references on how to instantiate and issue tokens
}
provider.registerGrantType(grantType, tokenExchangeHandler, parameters, allowedDuplicateParameters);
When using provider.app
or provider.callback
as a mounted application in your own koa or express
stack just follow the respective module's documentation. However, when using the provider.app
Koa
instance directly to register i.e. koa-helmet you must push the middleware in
front of oidc-provider in the middleware stack.
const helmet = require('koa-helmet');
// Correct, pushes koa-helmet at the end of the middleware stack but BEFORE oidc-provider.
provider.use(helmet());
// Incorrect, pushes koa-helmet at the end of the middleware stack AFTER oidc-provider, not being
// executed when errors are encountered or during actions that do not "await next()".
provider.app.use(helmet());
You can push custom middleware to be executed before and after oidc-provider.
provider.use(async (ctx, next) => {
/** pre-processing
* you may target a specific action here by matching `ctx.path`
*/
console.log('pre middleware', ctx.method, ctx.path);
await next();
/** post-processing
* since internal route matching was already executed you may target a specific action here
* checking `ctx.oidc.route`, the unique route names used are
*
* `authorization`
* `certificates`
* `check_session_origin`
* `check_session`
* `client_delete`
* `client_update`
* `client`
* `code_verification`
* `device_authorization`
* `device_resume`
* `discovery`
* `end_session`
* `end_session_confirm`
* `end_session_success`
* `introspection`
* `registration`
* `resume`
* `revocation`
* `token`
* `userinfo`
*/
console.log('post middleware', ctx.method, ctx.oidc.route);
});
The following snippets show how a provider instance can be mounted to existing applications with a path prefix.
// assumes express ^4.0.0
const prefix = '/oidc';
expressApp.use(prefix, oidc.callback);
// assumes koa ^2.0.0
const mount = require('koa-mount');
const prefix = '/oidc';
koaApp.use(mount(prefix, oidc.app));
Having a TLS offloading proxy in front of Node.js running oidc-provider is
the norm. To let your downstream application know of the original protocol and
ip you have to tell your app to trust x-forwarded-proto
and x-forwarded-for
headers commonly set by those proxies (as with any express/koa application).
This is needed for the provider responses to be correct (e.g. to have the right
https URL endpoints and keeping the right (secure) protocol).
Depending on your setup you should do the following in your downstream application code
setup | example |
---|---|
standalone oidc-provider | provider.proxy = true; |
oidc-provider mounted to a koa app | yourKoaApp.proxy = true |
oidc-provider mounted to an express app | provider.proxy = true; |
See http://koajs.com/#settings and the example.
It is also necessary that the web server doing the offloading also passes those headers to the downstream application. Here is a common configuration for Nginx (assuming that the downstream application is listening on 127.0.0.1:8009). Your configuration may vary, please consult your web server documentation for details.
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://127.0.0.1:8009;
proxy_redirect off;
}
The provided example and any new instance of oidc-provider will use the basic in-memory adapter for storing issued tokens, codes, user sessions, dynamically registered clients, etc. This is fine as long as you develop, configure and generally just play around since every time you restart your process all information will be lost. As soon as you cannot live with this limitation you will be required to provide your own custom adapter constructor for oidc-provider to use. This constructor will be called for every model accessed the first time it is needed. The API oidc-provider expects is documented here.
Array of objects representing client metadata. These clients are referred to as static, they don't expire, never reload, are always available. If the client metadata in this array is invalid the Provider instantiation will fail with an error. In addition to these clients the provider will use your adapter's find
method when a non-cached client_id is encountered. If you only wish to support statically configured clients and no dynamic registration then make it so that your adapter resolves client find calls with a falsy value (e.g. return Promise.resolve()
) and don't take unnecessary DB trips.
Client's metadata is validated as defined by the respective specification they've been defined in.
default value:
[]
(Click to expand) Available Metadata
application_type, client_id, client_name, client_secret, client_uri, contacts, default_acr_values, default_max_age, grant_types, id_token_signed_response_alg, initiate_login_uri, jwks, jwks_uri, logo_uri, policy_uri, post_logout_redirect_uris, redirect_uris, require_auth_time, response_types, scope, sector_identifier_uri, subject_type, token_endpoint_auth_method, tos_uri, userinfo_signed_response_alg
The following metadata is available but may not be recognized depending on your provider's configuration.
authorization_encrypted_response_alg, authorization_encrypted_response_enc, authorization_signed_response_alg, backchannel_logout_session_required, backchannel_logout_uri, frontchannel_logout_session_required, frontchannel_logout_uri, id_token_encrypted_response_alg, id_token_encrypted_response_enc, introspection_encrypted_response_alg, introspection_encrypted_response_enc, introspection_endpoint_auth_method, introspection_endpoint_auth_signing_alg, introspection_signed_response_alg, request_object_encryption_alg, request_object_encryption_enc, request_object_signing_alg, request_uris, revocation_endpoint_auth_method, revocation_endpoint_auth_signing_alg, tls_client_auth_san_dns, tls_client_auth_san_email, tls_client_auth_san_ip, tls_client_auth_san_uri, tls_client_auth_subject_dn, tls_client_certificate_bound_access_tokens, token_endpoint_auth_signing_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, web_message_uris
Helper used by the OP to load an account and retrieve its available claims. The return value should be a Promise and #claims() can return a Promise too
default value:
async findAccount(ctx, sub, token) {
// @param ctx - koa request context
// @param sub {string} - account identifier (subject)
// @param token - is a reference to the token used for which a given account is being loaded,
// is undefined in scenarios where claims are returned from authorization endpoint
return {
accountId: sub,
// @param use {string} - can either be "id_token" or "userinfo", depending on
// where the specific claims are intended to be put in
// @param scope {string} - the intended scope, while oidc-provider will mask
// claims depending on the scope automatically you might want to skip
// loading some claims from external resources or through db projection etc. based on this
// detail or not return them in ID Tokens but only UserInfo and so on
// @param claims {object} - the part of the claims authorization parameter for either
// "id_token" or "userinfo" (depends on the "use" param)
// @param rejected {Array[String]} - claim names that were rejected by the end-user, you might
// want to skip loading some claims from external resources or through db projection
async claims(use, scope, claims, rejected) {
return { sub };
},
};
}
JSON Web Key Set used by the provider for signing and encryption. The object must be in JWK Set format. All provided keys must be private keys. Note: Be sure to follow best practices for distributing private keying material and secrets for your respective target deployment environment.
Supported key types are:
- RSA
- OKP (Ed25519 and Ed448 curves)
- EC (P-256, P-384 and P-521 curves)
recommendation: Provider key rotation - The following action order is recommended when rotating signing keys on a distributed deployment with rolling reloads in place.
- push new keys at the very end of the "keys" array in your JWKS, this means the keys will become available for verification should they be encountered but not yet used for signing
- reload all your processes
- move your new key to the very front of the "keys" array in your JWKS, this means the key will be used for signing after reload
- reload all your processes
default value:
{
keys: [
{
alg: 'RS256',
d: 'VEZOsY07JTFzGTqv6cC2Y32vsfChind2I_TTuvV225_-0zrSej3XLRg8iE_u0-3GSgiGi4WImmTwmEgLo4Qp3uEcxCYbt4NMJC7fwT2i3dfRZjtZ4yJwFl0SIj8TgfQ8ptwZbFZUlcHGXZIr4nL8GXyQT0CK8wy4COfmymHrrUoyfZA154ql_OsoiupSUCRcKVvZj2JHL2KILsq_sh_l7g2dqAN8D7jYfJ58MkqlknBMa2-zi5I0-1JUOwztVNml_zGrp27UbEU60RqV3GHjoqwI6m01U7K0a8Q_SQAKYGqgepbAYOA-P4_TLl5KC4-WWBZu_rVfwgSENwWNEhw8oQ',
dp: 'E1Y-SN4bQqX7kP-bNgZ_gEv-pixJ5F_EGocHKfS56jtzRqQdTurrk4jIVpI-ZITA88lWAHxjD-OaoJUh9Jupd_lwD5Si80PyVxOMI2xaGQiF0lbKJfD38Sh8frRpgelZVaK_gm834B6SLfxKdNsP04DsJqGKktODF_fZeaGFPH0',
dq: 'F90JPxevQYOlAgEH0TUt1-3_hyxY6cfPRU2HQBaahyWrtCWpaOzenKZnvGFZdg-BuLVKjCchq3G_70OLE-XDP_ol0UTJmDTT-WyuJQdEMpt_WFF9yJGoeIu8yohfeLatU-67ukjghJ0s9CBzNE_LrGEV6Cup3FXywpSYZAV3iqc',
e: 'AQAB',
kid: 'keystore-CHANGE-ME',
kty: 'RSA',
n: 'xwQ72P9z9OYshiQ-ntDYaPnnfwG6u9JAdLMZ5o0dmjlcyrvwQRdoFIKPnO65Q8mh6F_LDSxjxa2Yzo_wdjhbPZLjfUJXgCzm54cClXzT5twzo7lzoAfaJlkTsoZc2HFWqmcri0BuzmTFLZx2Q7wYBm0pXHmQKF0V-C1O6NWfd4mfBhbM-I1tHYSpAMgarSm22WDMDx-WWI7TEzy2QhaBVaENW9BKaKkJklocAZCxk18WhR0fckIGiWiSM5FcU1PY2jfGsTmX505Ub7P5Dz75Ygqrutd5tFrcqyPAtPTFDk8X1InxkkUwpP3nFU5o50DGhwQolGYKPGtQ-ZtmbOfcWQ',
p: '5wC6nY6Ev5FqcLPCqn9fC6R9KUuBej6NaAVOKW7GXiOJAq2WrileGKfMc9kIny20zW3uWkRLm-O-3Yzze1zFpxmqvsvCxZ5ERVZ6leiNXSu3tez71ZZwp0O9gys4knjrI-9w46l_vFuRtjL6XEeFfHEZFaNJpz-lcnb3w0okrbM',
q: '3I1qeEDslZFB8iNfpKAdWtz_Wzm6-jayT_V6aIvhvMj5mnU-Xpj75zLPQSGa9wunMlOoZW9w1wDO1FVuDhwzeOJaTm-Ds0MezeC4U6nVGyyDHb4CUA3ml2tzt4yLrqGYMT7XbADSvuWYADHw79OFjEi4T3s3tJymhaBvy1ulv8M',
qi: 'wSbXte9PcPtr788e713KHQ4waE26CzoXx-JNOgN0iqJMN6C4_XJEX-cSvCZDf4rh7xpXN6SGLVd5ibIyDJi7bbi5EQ5AXjazPbLBjRthcGXsIuZ3AtQyR0CEWNSdM7EyM5TRdyZQ9kftfz9nI03guW3iKKASETqX2vh0Z8XRjyU',
use: 'sig'
}
]
}
(Click to expand) Generating keys
const { JWKS: { KeyStore } } = require('@panva/jose');
const keystore = new KeyStore();
keystore.generateSync('RSA', 2048, {
alg: 'RS256',
use: 'sig',
});
console.log('this is the full private JWKS:\n', keystore.toJWKS(true));
(Click to expand) Generating keys for both signing and encryption
Re-using the same keys for both encryption and signing is discouraged so it is best to generate one with { use: 'sig' }
and another with { use: 'enc' }
, e.g.
const { JWKS: { KeyStore } } = require('@panva/jose');
const keystore = new KeyStore();
Promise.all([
keystore.generate('RSA', 2048, {
use: 'sig',
}),
keystore.generate('RSA', 2048, {
use: 'enc',
}),
keystore.generate('EC', 'P-256', {
use: 'sig',
}),
keystore.generate('EC', 'P-256', {
use: 'enc',
}),
keystore.generate('OKP', 'Ed25519', {
use: 'sig',
}),
]).then(function () {
console.log('this is the full private JWKS:\n', keystore.toJWKS(true));
});
Enable/disable features. Some features are still either based on draft or experimental RFCs. Enabling those will produce a warning in your console and you must be aware that breaking changes may occur between draft implementations and that those will be published as minor versions of oidc-provider. See the example below on how to acknowledge the specification is a draft (this will remove the warning log) and ensure the provider instance will fail to instantiate if a new version of oidc-provider bundles newer version of the RFC with breaking changes in it.
(Click to expand) Acknowledging a draft / experimental feature
new Provider('http://localhost:3000', {
features: {
webMessageResponseMode: {
enabled: true,
},
},
});
// The above code produces this NOTICE
// NOTICE: The following draft features are enabled and their implemented version not acknowledged
// NOTICE: - OAuth 2.0 Web Message Response Mode - draft 00 (This is an Individual draft. URL: https://tools.ietf.org/html/draft-sakimura-oauth-wmrm-00)
// NOTICE: Breaking changes between draft version updates may occur and these will be published as MINOR semver oidc-provider updates.
// NOTICE: You may disable this notice and these potentially breaking updates by acknowledging the current draft version. See https://github.com/panva/node-oidc-provider/tree/master/docs/README.md#features
new Provider('http://localhost:3000', {
features: {
webMessageResponseMode: {
enabled: true,
ack: 0, // < we're acknowledging draft 00 of the RFC
},
},
});
// No more NOTICE, at this point if the draft implementation changed to 01 and contained no breaking
// changes, you're good to go, still no NOTICE, your code is safe to run.
// Now lets assume you upgrade oidc-provider version and it bundles draft 02 and it contains breaking
// changes
new Provider('http://localhost:3000', {
features: {
webMessageResponseMode: {
enabled: true,
ack: 0, // < bundled is 2, but we're still acknowledging 0
},
},
});
// Thrown:
// Error: An unacknowledged version of a draft feature is included in this oidc-provider version.
Back-Channel Logout 1.0 - draft 04
Enables Back-Channel Logout features.
default value:
{
enabled: false
}
draft-ietf-oauth-mtls-12 - OAuth 2.0 Mutual TLS Client Authentication and Certificate Bound Access Tokens
Enables Certificate Bound Access Tokens. Clients may be registered with tls_client_certificate_bound_access_tokens
to indicate intention to receive mutual TLS client certificate bound access tokens.
default value:
{
enabled: false
}
(Click to expand) Setting up the environment for Certificate Bound Access Tokens
To enable Certificate Bound Access Tokens the provider expects your TLS-offloading proxy to handle the client certificate validation, parsing, handling, etc. Once set up you are expected to forward x-ssl-client-cert
header with variable values set by this proxy. An important aspect is to sanitize the inbound request headers at the proxy.
The most common openssl based proxies are Apache and NGINX, with those you're looking to use
SSLVerifyClient
(Apache) / ssl_verify_client
(NGINX) with the appropriate configuration value that matches your setup requirements.
Set the proxy request header with variable set as a result of enabling mutual TLS
# NGINX
proxy_set_header x-ssl-client-cert $ssl_client_cert;
# Apache
RequestHeader set x-ssl-client-cert ""
RequestHeader set x-ssl-client-cert "%{SSL_CLIENT_CERT}s"
You should also consider hosting the endpoints supporting client authentication, on a separate host name or port in order to prevent unintended impact on the TLS behaviour of your other endpoints, e.g. Discovery or the authorization endpoint, by updating the discovery response to add draft-ietf-oauth-mtls-12 specified mtls_endpoint_aliases
.
provider.use(async (ctx, next) => {
await next();
if (ctx.oidc.route === 'discovery') {
ctx.body.mtls_endpoint_aliases = {};
const endpointAuthMethodKeys = [
'token_endpoint_auth_methods_supported',
'introspection_endpoint_auth_methods_supported',
'revocation_endpoint_auth_methods_supported',
];
// splits `*_endpoint_auth_methods_supported` into two namespaces (mutual-TLS and regular);
endpointAuthMethodKeys.forEach((key) => {
if (ctx.body[key]) {
ctx.body.mtls_endpoint_aliases[key] = ctx.body[key].filter(k => k.endsWith('tls_client_auth'));
ctx.body[key] = ctx.body[key].filter(k => !ctx.body.mtls_endpoint_aliases[key].includes(k));
}
});
const mtlsEndpoints = [
'userinfo_endpoint',
'token_endpoint',
'introspection_endpoint',
'revocation_endpoint',
'device_authorization_endpoint',
];
// aliases endpoints accepting client certificates in `mtls_endpoint_aliases`
const mtlsOrigin = 'https://mtls.op.example.com';
mtlsEndpoints.forEach((key) => {
if (ctx.body[key]) {
ctx.body.mtls_endpoint_aliases[key] = `${mtlsOrigin}${url.parse(ctx.body[key]).pathname}`;
}
});
}
});
When doing that be sure to remove the client provided headers of the same name on the non-mutual TLS enabled host name / port in your proxy setup or block the routes for these there completely.
Core 1.0 - Requesting Claims using the "claims" Request Parameter
Enables the use and validations of claims
parameter as described in the specification.
default value:
{
enabled: false
}
RFC6749 - Client Credentials
Enables grant_type=client_credentials
to be used on the token endpoint.
default value:
{
enabled: false
}
Development-ONLY out of the box interaction views bundled with the library allow you to skip the boring frontend part while experimenting with oidc-provider. Enter any username (will be used as sub claim value) and any password to proceed.
Be sure to disable and replace this feature with your actual frontend flows and End-User authentication flows as soon as possible. These views are not meant to ever be seen by actual users.
default value:
{
enabled: true
}
draft-ietf-oauth-device-flow-15 - OAuth 2.0 Device Authorization Grant
Enables Device Authorization Grant
default value:
{
charset: 'base-20',
deviceInfo: [Function: deviceInfo], // see expanded details below
enabled: false,
mask: '****-****',
successSource: [AsyncFunction: successSource], // see expanded details below
userCodeConfirmSource: [AsyncFunction: userCodeConfirmSource], // see expanded details below
userCodeInputSource: [AsyncFunction: userCodeInputSource]
}
(Click to expand) features.deviceFlow options details
alias for a character set of the generated user codes. Supported values are
base-20
uses BCDFGHJKLMNPQRSTVWXZdigits
uses 0123456789
default value:
'base-20'
Helper function used to extract details from the device authorization endpoint request. This is then available during the end-user confirm screen and is supposed to aid the user confirm that the particular authorization initiated by the user from a device in his possession
default value:
deviceInfo(ctx) {
return {
ip: ctx.ip,
ua: ctx.get('user-agent'),
};
}
a string used as a template for the generated user codes, *
characters will be replaced by random chars from the charset, -
(dash) and
(space) characters may be included for readability. See the RFC for details about minimal recommended entropy
default value:
'****-****'
HTML source rendered when device code feature renders a success page for the User-Agent.
default value:
async successSource(ctx) {
// @param ctx - koa request context
const {
clientId, clientName, clientUri, initiateLoginUri, logoUri, policyUri, tosUri,
} = ctx.oidc.client;
ctx.body = `<!DOCTYPE html>
ead>
<title>Sign-in Success</title>
<style>/* css and html classes omitted for brevity, see lib/helpers/defaults.js */</style>
head>
ody>
<div>
<h1>Sign-in Success</h1>
<p>Your sign-in ${clientName ? `with ${clientName}` : ''} was successful, you can now close this page.</p>
</div>
body>
html>`;
}
HTML source rendered when device code feature renders an a confirmation prompt for ther User-Agent.
default value:
async userCodeConfirmSource(ctx, form, client, deviceInfo, userCode) {
// @param ctx - koa request context
// @param form - form source (id="op.deviceConfirmForm") to be embedded in the page and
// submitted by the End-User.
// @param deviceInfo - device information from the device_authorization_endpoint call
// @param userCode - formatted user code by the configured mask
const {
clientId, clientName, clientUri, logoUri, policyUri, tosUri,
} = ctx.oidc.client;
ctx.body = `<!DOCTYPE html>
ead>
<title>Device Login Confirmation</title>
<style>/* css and html classes omitted for brevity, see lib/helpers/defaults.js */</style>
head>
ody>
<div>
<h1>Confirm Device</h1>
<p>
<strong>${clientName || clientId}</strong>
<br/><br/>
The following code should be displayed on your device<br/><br/>
<code>${userCode}</code>
<br/><br/>
<small>If you did not initiate this action, the code does not match or are unaware of such device in your possession please close this window or click abort.</small>
</p>
${form}
<button autofocus type="submit" form="op.deviceConfirmForm">Continue</button>
<div>
<button type="submit" form="op.deviceConfirmForm" value="yes" name="abort">[ Abort ]</button>
</div>
</div>
body>
html>`;
}
HTML source rendered when device code feature renders an input prompt for the User-Agent.
default value:
async userCodeInputSource(ctx, form, out, err) {
// @param ctx - koa request context
// @param form - form source (id="op.deviceInputForm") to be embedded in the page and submitted
// by the End-User.
// @param out - if an error is returned the out object contains details that are fit to be
// rendered, i.e. does not include internal error messages
// @param err - error object with an optional userCode property passed when the form is being
// re-rendered due to code missing/invalid/expired
let msg;
if (err && (err.userCode || err.name === 'NoCodeError')) {
msg = '<p>The code you entered is incorrect. Try again</p>';
} else if (err && err.name === 'AbortedError') {
msg = '<p>The Sign-in request was interrupted</p>';
} else if (err) {
msg = '<p>There was an error processing your request</p>';
} else {
msg = '<p>Enter the code displayed on your device</p>';
}
ctx.body = `<!DOCTYPE html>
ead>
<title>Sign-in</title>
<style>/* css and html classes omitted for brevity, see lib/helpers/defaults.js */</style>
head>
ody>
<div>
<h1>Sign-in</h1>
${msg}
${form}
<button type="submit" form="op.deviceInputForm">Continue</button>
</div>
body>
html>`;
}
Enables encryption features such as receiving encrypted UserInfo responses, encrypted ID Tokens and allow receiving encrypted Request Objects.
default value:
{
enabled: false
}
Front-Channel Logout 1.0 - draft 02
Enables Front-Channel Logout features
default value:
{
enabled: false,
logoutPendingSource: [AsyncFunction: logoutPendingSource]
}
(Click to expand) features.frontchannelLogout options details
HTML source rendered when there are pending front-channel logout iframes to be called to trigger RP logouts. It should handle waiting for the frames to be loaded as well as have a timeout mechanism in it.
default value:
async logoutPendingSource(ctx, frames, postLogoutRedirectUri) {
ctx.body = `<!DOCTYPE html>
ead>
<title>Logout</title>
<style>/* css and html classes omitted for brevity, see lib/helpers/defaults.js */</style>
head>
ody>
${frames.join('')}
<script>
var loaded = 0;
function redirect() {
window.location.replace("${postLogoutRedirectUri}");
}
function frameOnLoad() {
loaded += 1;
if (loaded === ${frames.length}) {
redirect();
}
}
Array.prototype.slice.call(document.querySelectorAll('iframe')).forEach(function (element) {
element.onload = frameOnLoad;
});
setTimeout(redirect, 2500);
</script>
<noscript>
Your browser does not support JavaScript or you've disabled it.<br/>
<a href="${postLogoutRedirectUri}">Continue</a>
</noscript>
body>
html>`;
}
RFC7662 - OAuth 2.0 Token Introspection
Enables Token Introspection features
default value:
{
enabled: false
}
draft-ietf-oauth-jwt-introspection-response-02 - JWT Response for OAuth Token Introspection
Enables JWT responses for Token Introspection features
default value:
{
enabled: false
}
openid-financial-api-jarm-wd-02 - JWT Secured Authorization Response Mode (JARM)
Enables JWT Secured Authorization Responses
default value:
{
enabled: false
}
Dynamic Client Registration 1.0
Enables Dynamic Client Registration.
default value:
{
enabled: false,
idFactory: [Function: idFactory], // see expanded details below
initialAccessToken: false,
policies: undefined,
secretFactory: [Function: secretFactory]
}
(Click to expand) features.registration options details
helper generating random client identifiers during dynamic client registration
default value:
idFactory() {
return nanoid();
}
Enables registration_endpoint to check a valid initial access token is provided as a bearer token during the registration call. Supported types are
string
the string value will be checked as a static initial access tokenboolean
true/false to enable/disable adapter backed initial access tokens
default value:
false
(Click to expand) To add an adapter backed initial access token and retrive its value
new (provider.InitialAccessToken)({}).save().then(console.log);
define registration and registration management policies applied to client properties. Policies are sync/async functions that are assigned to an Initial Access Token that run before the regular client property validations are run. Multiple policies may be assigned to an Initial Access Token and by default the same policies will transfer over to the Registration Access Token. A policy may throw / reject and it may modify the properties object.
default value:
undefined
(Click to expand) To define registration and registration management policies
To define policy functions configure features.registration
to be an object like so:
{
enabled: true,
initialAccessToken: true, // to enable adapter-backed initial access tokens
policies: {
'my-policy': function (ctx, properties) {
// @param ctx - koa request context
// @param properties - the client properties which are about to be validated
// example of setting a default
if (!('client_name' in properties)) {
properties.client_name = generateRandomClientName();
}
// example of forcing a value
properties.userinfo_signed_response_alg = 'RS256';
// example of throwing a validation error
if (someCondition(ctx, properties)) {
throw new Provider.errors.InvalidClientMetadata('validation error message');
}
},
'my-policy-2': async function (ctx, properties) {},
},
}
An Initial Access Token with those policies being executed (one by one in that order) is created like so
new (provider.InitialAccessToken)({ policies: ['my-policy', 'my-policy-2'] }).save().then(console.log);
Note: referenced policies must always be present when encountered on a token, an AssertionError will be thrown inside the request context if it is not, resulting in a 500 Server Error. Note: the same policies will be assigned to the Registration Access Token after a successful validation. If you wish to assign different policies to the Registration Access Token
// inside your final ran policy
ctx.oidc.entities.RegistrationAccessToken.policies = ['update-policy'];
(Click to expand) Using Initial Access Token policies for software_statement dynamic client registration property
Support modules:
const { verify } = require('jsonwebtoken');
const {
errors: { InvalidSoftwareStatement, UnapprovedSoftwareStatement, InvalidClientMetadata },
} = require('oidc-provider');
features.registration configuration:
{
enabled: true,
initialAccessToken: true, // to enable adapter-backed initial access tokens
policies: {
'softwareStatement': async function (ctx, metadata) {
if (!('software_statement' in metadata)) {
throw new InvalidClientMetadata('software_statement must be provided');
}
const softwareStatementKey = await loadKeyForThisPolicy();
const statement = metadata.software_statement;
let payload;
try {
payload = verify(value, softwareStatementKey, {
algorithms: ['RS256'],
issuer: 'Software Statement Issuer',
});
if (!approvedStatement(value, payload)) {
throw new UnapprovedSoftwareStatement('software_statement not approved for use');
}
// cherry pick the software_statement values and assign them
// Note: regular validations will run!
const { client_name, client_uri } = payload;
Object.assign(metadata, { client_name, client_uri });
} catch (err) {
throw new InvalidSoftwareStatement('could not verify software_statement');
}
},
},
}
An Initial Access Token that requires and validates the given software statement is created like so
new (provider.InitialAccessToken)({ policies: ['softwareStatement'] }).save().then(console.log);
helper generating random client secrets during dynamic client registration
default value:
secretFactory() {
return base64url(crypto.randomBytes(64)); // 512 base64url random bits
}
OAuth 2.0 Dynamic Client Registration Management Protocol
Enables Update and Delete features described in the RFC
default value:
{
enabled: false,
rotateRegistrationAccessToken: false
}
(Click to expand) features.registrationManagement options details
Enables registration access token rotation. The provider will discard the current Registration Access Token with a successful update and issue a new one, returning it to the client with the Registration Update Response. Supported values are
false
registration access tokens are not rotatedtrue
registration access tokens are rotated when used- function returning true/false, true when rotation should occur, false when it shouldn't
default value:
false
(Click to expand) function use
{
features: {
registrationManagement: {
enabled: true,
async rotateRegistrationAccessToken(ctx) {
// return tokenRecentlyRotated(ctx.oidc.entities.RegistrationAccessToken);
// or
// return customClientBasedPolicy(ctx.oidc.entities.Client);
}
}
}
}
Core 1.0 - Passing a Request Object by Value
Enables the use and validations of request
parameter
default value:
{
enabled: false
}
Core 1.0 - Passing a Request Object by Reference
Enables the use and validations of request_uri
parameter
default value:
{
enabled: true,
requireUriRegistration: true
}
(Click to expand) features.requestUri options details
makes request_uri pre-registration mandatory/optional
default value:
true
draft-ietf-oauth-resource-indicators-02 - Resource Indicators for OAuth 2.0
Enables the use of resource
parameter for the authorization and token endpoints. In order for the feature to be any useful you must also use the audiences
helper function to validate the resource(s) and transform it to jwt's token audience.
default value:
{
enabled: false
}
(Click to expand) Example use
This example will
- throw based on an OP policy when unrecognized or unauthorized resources are requested
- transform resources to audience and push them down to the audience of access tokens
- take both, the parameter and previously granted resources into consideration
// const { InvalidTarget } = Provider.errors;
// `resourceAllowedForClient` is the custom OP policy
// `transform` is mapping the resource values to actual aud values
{
// ...
async function audiences(ctx, sub, token, use) {
if (use === 'access_token') {
const { oidc: { route, client, params: { resource: resourceParam } } } = ctx;
let grantedResource;
if (route === 'token') {
const { oidc: { params: { grant_type } } } = ctx;
switch (grant_type) {
case 'authorization_code':
grantedResource = ctx.oidc.entities.AuthorizationCode.resource;
break;
case 'refresh_token':
grantedResource = ctx.oidc.entities.RefreshToken.resource;
break;
case 'urn:ietf:params:oauth:grant-type:device_code':
grantedResource = ctx.oidc.entities.DeviceCode.resource;
break;
default:
}
}
const allowed = await resourceAllowedForClient(resourceParam, grantedResource, client);
if (!allowed) {
throw new InvalidResource('unauthorized "resource" requested');
}
// => array of validated and transformed string audiences or undefined if no audiences
// are to be listed
return transform(resourceParam, grantedResource);
}
},
formats: {
default: 'opaque',
AccessToken(ctx, token) {
if (Array.isArray(token.aud)) {
return 'jwt';
}
return 'opaque';
}
},
// ...
}
RFC7009 - OAuth 2.0 Token Revocation
Enables Token Revocation
default value:
{
enabled: false
}
Session Management 1.0 - draft 28
Enables Session Management features.
default value:
{
enabled: false,
keepHeaders: false
}
(Click to expand) features.sessionManagement options details
Enables/Disables removing frame-ancestors from Content-Security-Policy and X-Frame-Options headers.
recommendation: Only enable this if you know what you're doing either in a followup middleware or your app server, otherwise you shouldn't have the need to touch this option.
default value:
false
draft-sakimura-oauth-wmrm-00 - OAuth 2.0 Web Message Response Mode
Enables web_message
response mode.
Note: Although a general advise to use a helmet
(express, koa) it is especially advised for your interaction views routes if Web Message Response Mode is available on your deployment.
default value:
{
enabled: false
}
Array of strings, the Authentication Context Class References that OP supports.
default value:
[]
Helper used by the OP to push additional audiences to issued Access and ClientCredentials Tokens. The return value should either be falsy to omit adding additional audiences or an array of strings to push.
default value:
async audiences(ctx, sub, token, use) {
// @param ctx - koa request context
// @param sub - account identifier (subject)
// @param token - the token to which these additional audiences will be passed to
// @param use - can be one of "access_token" or "client_credentials"
// depending on where the specific audiences are intended to be put in
return undefined;
}
Array of the Claim Names of the Claims that the OpenID Provider MAY be able to supply values for.
default value:
{
acr: null,
auth_time: null,
iss: null,
openid: [
'sub'
],
sid: null
}
Helper function used to check whether a given CORS request should be allowed based on the request's client.
default value:
clientBasedCORS(ctx, origin, client) {
return true;
}
Default client metadata to be assigned when unspecified by the client metadata, e.g. During Dynamic Client Registration or for statically configured clients. The default value does not represent all default values, but merely copies its subset. You can provide any used client metadata property in this object.
default value:
{
grant_types: [
'authorization_code'
],
id_token_signed_response_alg: 'RS256',
response_types: [
'code'
],
token_endpoint_auth_method: 'client_secret_basic'
}
(Click to expand) Changing the default client token_endpoint_auth_method
To change the default client token_endpoint_auth_method configure clientDefaults
to be an object like so:
{
token_endpoint_auth_method: 'client_secret_post'
}
(Click to expand) Changing the default client response type to `code id_token`
To change the default client response_types configure clientDefaults
to be an object like so:
{
response_types: ['code id_token'],
grant_types: ['authorization_code', 'implicit'],
}
A Number
value (in seconds) describing the allowed system clock skew for validating client-provided JWTs, e.g. Request objects and otherwise comparing timestamps
recommendation: Only set this to a reasonable value when needed to cover server-side client and oidc-provider server clock skew. More than 5 minutes (if needed) is probably a sign something else is wrong.
default value:
0
ID Token only contains End-User claims when the requested response_type
is id_token
Core 1.0 - 5.4. Requesting Claims using Scope Values defines that claims requested using the scope
parameter are only returned from the UserInfo Endpoint unless the response_type
is id_token
. This is the default oidc-provider behaviour, you can turn this behaviour off and return End-User claims with all ID Tokens by providing this configuration as false
.
default value:
true
Options for the cookie module used by the OP to keep track of various User-Agent states.
Keygrip Signing keys used for cookie signing to prevent tampering.
recommendation: Rotate regularly (by prepending new keys) with a reasonable interval and keep a reasonable history of keys to allow for returning user session cookies to still be valid and re-signed
default value:
[]
Options for long-term cookies
recommendation: set cookies.keys and cookies.long.signed = true
default value:
{
httpOnly: true,
maxAge: 1209600000,
overwrite: true,
sameSite: 'none'
}
Cookie names used by the OP to store and transfer various states.
default value:
{
interaction: '_interaction',
resume: '_interaction_resume',
session: '_session',
state: '_state'
}
Options for short-term cookies
recommendation: set cookies.keys and cookies.short.signed = true
default value:
{
httpOnly: true,
maxAge: 600000,
overwrite: true,
sameSite: 'lax'
}
Pass additional properties to this object to extend the discovery document
default value:
{
claim_types_supported: [
'normal'
],
claims_locales_supported: undefined,
display_values_supported: undefined,
op_policy_uri: undefined,
op_tos_uri: undefined,
service_documentation: undefined,
ui_locales_supported: undefined
}
Array of the dynamic scope values that the OP supports. These must be regular expressions that the OP will check string scope values, that aren't in the static list, against.
default value:
[]
(Click to expand) To enable a dynamic scope values like `api:write:{hex id}` and `api:read:{hex id}`
Configure dynamicScopes
like so:
[
/^api:write:[a-fA-F0-9]{2,}$/,
/^api:read:[a-fA-F0-9]{2,}$/,
]
Helper used by the OP to decide whether the given authorization code/ device code or implicit returned access token be bound to the user session. This will be applied to all tokens issued from the authorization / device code in the future. When tokens are session-bound the session will be loaded by its uid
every time the token is encountered. Session bound tokens will effectively get revoked if the end-user logs out.
default value:
async expiresWithSession(ctx, token) {
return !token.scopes.has('offline_access');
}
Allows for custom client metadata to be defined, validated, manipulated as well as for existing property validations to be extended
Array of property names that clients will be allowed to have defined. Property names will have to strictly follow the ones defined here. However, on a Client instance property names will be snakeCased.
default value:
[]
validator function that will be executed in order once for every property defined in extraClientMetadata.properties
, regardless of its value or presence on the client metadata passed in. Must be synchronous, async validators or functions returning Promise will be rejected during runtime. To modify the current client metadata values (for current key or any other) just modify the passed in metadata
argument.
default value:
validator(key, value, metadata) {
// validations for key, value, other related metadata
// throw new Provider.errors.InvalidClientMetadata() to reject the client metadata (see all
// errors on Provider.errors)
// metadata[key] = value; to assign values
// return not necessary, metadata is already a reference.
}
(Click to expand) Using extraClientMetadata to allow software_statement dynamic client registration property
const { verify } = require('jsonwebtoken');
const {
errors: { InvalidSoftwareStatement, UnapprovedSoftwareStatement },
} = require('oidc-provider');
const softwareStatementKey = require('path/to/public/key');
{
extraClientMetadata: {
properties: ['software_statement'],
validator(key, value, metadata) {
if (key === 'software_statement') {
if (value === undefined) return;
// software_statement is not stored, but used to convey client metadata
delete metadata.software_statement;
let payload;
try {
// extraClientMetadata.validator must be sync :sadface:
payload = verify(value, softwareStatementKey, {
algorithms: ['RS256'],
issuer: 'Software Statement Issuer',
});
if (!approvedStatement(value, payload)) {
throw new UnapprovedSoftwareStatement('software_statement not approved for use');
}
// cherry pick the software_statement values and assign them
// Note: there will be no further validation ran on those values, so make sure
// they're conform
const { client_name, client_uri } = payload;
Object.assign(metadata, { client_name, client_uri });
} catch (err) {
throw new InvalidSoftwareStatement('could not verify software_statement');
}
}
}
}
}
Pass an iterable object (i.e. Array or Set of strings) to extend the parameters recognised by the authorization and device authorization endpoints. These parameters are then available in ctx.oidc.params
as well as passed to interaction session details
default value:
[]
This option allows to configure the token storage and value formats. The different values change how a client-facing token value is generated as well as what properties get sent to the adapter for storage.
opaque
(default) formatted tokens store every property as a root property in your adapterjwt
formatted tokens are issued as JWTs and stored the same asopaque
only with additional propertyjwt
. The signing algorithm for these tokens uses the client'sid_token_signed_response_alg
value and falls back toRS256
for tokens with no relation to a client or when the client's alg isnone
- the value may also be a function dynamically determining the format (returning either
jwt
oropaque
depending on the token itself)
default value:
{
AccessToken: undefined,
ClientCredentials: undefined,
extraJwtAccessTokenClaims: [AsyncFunction: extraJwtAccessTokenClaims]
}
(Click to expand) To enable JWT Access Tokens
Configure formats
:
{ AccessToken: 'jwt' }
(Click to expand) To dynamically decide on the format used, e.g. only if it is intended for more audiences
Configure formats
:
{
AccessToken(ctx, token) {
if (Array.isArray(token.aud)) {
return 'jwt';
}
return 'opaque';
}
}
helper function used by the OP to get additional JWT formatted token claims when it is being created
default value:
async extraJwtAccessTokenClaims(ctx, token) {
return undefined;
}
(Click to expand) To push additional claims to a JWT format Access Token
{
formats: {
AccessToken: 'jwt',
async extraJwtAccessTokenClaims(ctx, token) {
return {
preferred_username: 'johnny',
};
}
}
}
Helper called whenever the provider calls an external HTTP(S) resource. Use to change the got library's request options as they happen. This can be used to e.g. Change the request timeout option or to configure the global agent to use HTTP_PROXY and HTTPS_PROXY environment variables.
default value:
httpOptions(options) {
options.followRedirect = false;
options.headers['User-Agent'] = 'oidc-provider/${VERSION} (${ISSUER_IDENTIFIER})';
options.retry = 0;
options.throwHttpErrors = false;
options.timeout = 2500;
return options;
}
(Click to expand) To change the request's timeout
To change all request's timeout configure the httpOptions as a function like so:
{
httpOptions(options) {
options.timeout = 5000;
return options;
}
}
Helper used by the OP to determine where to redirect User-Agent for necessary interaction, can return both absolute and relative urls
default value:
async interactionUrl(ctx, interaction) {
return `/interaction/${ctx.oidc.uid}`;
}
structure of Prompts and their checks formed by Prompt and Check class instances. The default you can modify and the classes are available under Provider.interaction
.
default value:
[
Prompt {
name: 'login',
requestable: true,
details: (ctx) => {
const { oidc } = ctx;
return {
...(oidc.params.max_age === undefined ? { max_age: oidc.params.max_age } : undefined),
...(oidc.params.login_hint === undefined ? { login_hint: oidc.params.login_hint } : undefined),
...(oidc.params.id_token_hint === undefined ? { id_token_hint: oidc.params.id_token_hint } : undefined),
};
},
checks: [
Check {
reason: 'login_prompt',
description: 'login prompt was not resolved',
error: 'login_required',
details: () => {},
check: (ctx) => {
const { oidc } = ctx;
if (oidc.prompts.has(name) && oidc.promptPending(name)) {
return true;
}
return false;
}
},
Check {
reason: 'no_session',
description: 'End-User authentication is required',
error: 'login_required',
details: () => {},
check: (ctx) => {
const { oidc } = ctx;
if (oidc.session.accountId()) {
return false;
}
return true;
}
},
Check {
reason: 'max_age',
description: 'End-User authentication could not be obtained',
error: 'login_required',
details: () => {},
check: (ctx) => {
const { oidc } = ctx;
if (oidc.params.max_age === undefined) {
return false;
}
if (!oidc.session.accountId()) {
return true;
}
if (oidc.session.past(oidc.params.max_age)) {
return true;
}
return false;
}
},
Check {
reason: 'id_token_hint',
description: 'id_token_hint and authenticated subject do not match',
error: 'login_required',
details: () => {},
check: async (ctx) => {
const { oidc } = ctx;
const hint = oidc.params.id_token_hint;
if (hint === undefined) {
return false;
}
let payload;
try {
({ payload } = await oidc.provider.IdToken.validate(hint, oidc.client));
} catch (err) {
throw new errors.InvalidRequest(`could not validate id_token_hint (${err.message})`);
}
let sub = oidc.session.accountId();
if (sub === undefined) {
return true;
}
if (oidc.client.sectorIdentifier) {
sub = await instance(oidc.provider).configuration('pairwiseIdentifier')(ctx, sub, oidc.client);
}
if (payload.sub !== sub) {
return true;
}
return false;
}
},
Check {
reason: 'claims_id_token_sub_value',
description: 'requested subject could not be obtained',
error: 'login_required',
details: ({ oidc }) => ({ sub: oidc.claims.id_token.sub }),
check: async (ctx) => {
const { oidc } = ctx;
if (!has(oidc.claims, 'id_token.sub.value')) {
return false;
}
let sub = oidc.session.accountId();
if (sub === undefined) {
return true;
}
if (oidc.client.sectorIdentifier) {
sub = await instance(oidc.provider).configuration('pairwiseIdentifier')(ctx, sub, oidc.client);
}
if (oidc.claims.id_token.sub.value !== sub) {
return true;
}
return false;
}
},
Check {
reason: 'essential_acrs',
description: 'none of the requested ACRs could not be obtained',
error: 'login_required',
details: ({ oidc }) => ({ acr: oidc.claims.id_token.acr }),
check: (ctx) => {
const { oidc } = ctx;
const request = get(oidc.claims, 'id_token.acr', {});
if (!request || !request.essential || !request.values) {
return false;
}
if (!Array.isArray(oidc.claims.id_token.acr.values)) {
throw new errors.InvalidRequest('invalid claims.id_token.acr.values type');
}
if (request.values.includes(oidc.acr)) {
return false;
}
return true;
}
},
Check {
reason: 'essential_acr',
description: 'requested ACR could not be obtained',
error: 'login_required',
details: ({ oidc }) => ({ acr: oidc.claims.id_token.acr }),
check: (ctx) => {
const { oidc } = ctx;
const request = get(oidc.claims, 'id_token.acr', {});
if (!request || !request.essential || !request.value) {
return false;
}
if (request.value === oidc.acr) {
return false;
}
return true;
}
}
]
},
Prompt {
name: 'consent',
requestable: true,
details: (ctx) => {
const { oidc } = ctx;
const acceptedScopes = oidc.session.acceptedScopesFor(oidc.params.client_id);
const rejectedScopes = oidc.session.rejectedScopesFor(oidc.params.client_id);
const acceptedClaims = oidc.session.acceptedClaimsFor(oidc.params.client_id);
const rejectedClaims = oidc.session.rejectedClaimsFor(oidc.params.client_id);
const details = {
scopes: {
new: [...oidc.requestParamScopes]
.filter(x => !acceptedScopes.has(x) && !rejectedScopes.has(x)),
accepted: [...acceptedScopes],
rejected: [...rejectedScopes],
},
claims: {
new: [...oidc.requestParamClaims]
.filter(x => !acceptedClaims.has(x) && !rejectedClaims.has(x)),
accepted: [...acceptedClaims],
rejected: [...rejectedClaims],
},
};
return omitBy(details, val => val === undefined);
},
checks: [
Check {
reason: 'consent_prompt',
description: 'consent prompt was not resolved',
error: 'consent_required',
details: () => {},
check: (ctx) => {
const { oidc } = ctx;
if (oidc.prompts.has(name) && oidc.promptPending(name)) {
return true;
}
return false;
}
},
Check {
reason: 'client_not_authorized',
description: 'client not authorized for End-User session yet',
error: 'interaction_required',
details: () => {},
check: (ctx) => {
const { oidc } = ctx;
if (oidc.session.sidFor(oidc.client.clientId)) {
return false;
}
return true;
}
},
Check {
reason: 'native_client_prompt',
description: 'native clients require End-User interaction',
error: 'interaction_required',
details: () => {},
check: (ctx) => {
const { oidc } = ctx;
if (
oidc.client.applicationType === 'native'
&& oidc.params.response_type !== 'none'
&& (!oidc.result || !('consent' in oidc.result))
) {
return true;
}
return false;
}
},
Check {
reason: 'scopes_missing',
description: 'requested scopes not granted by End-User',
error: 'consent_required',
details: () => {},
check: (ctx) => {
const { oidc } = ctx;
const promptedScopes = oidc.session.promptedScopesFor(oidc.client.clientId);
for (const scope of oidc.requestParamScopes) { // eslint-disable-line no-restricted-syntax
if (!promptedScopes.has(scope)) {
return true;
}
}
return false;
}
},
Check {
reason: 'claims_missing',
description: 'requested claims not granted by End-User',
error: 'consent_required',
details: () => {},
check: (ctx) => {
const { oidc } = ctx;
const promptedClaims = oidc.session.promptedClaimsFor(oidc.client.clientId);
for (const claim of oidc.requestParamClaims) { // eslint-disable-line no-restricted-syntax
if (!promptedClaims.has(claim) && !['sub', 'sid', 'auth_time', 'acr', 'amr', 'iss'].includes(claim)) {
return true;
}
}
return false;
}
}
]
}
]
(Click to expand) configuring prompts
const { interaction: { Prompt, Check, DEFAULT } } = require('oidc-provider');
// DEFAULT.get(name) => returns a Prompt instance by its name
// DEFAULT.remove(name) => removes a Prompt instance by its name
// DEFAULT.add(prompt, index) => adds a Prompt instance to a specific index, default is to last index
// prompt.checks.get(reason) => returns a Check instance by its reason
// prompt.checks.remove(reason) => removes a Check instance by its reason
// prompt.checks.add(check, index) => adds a Check instance to a specific index, default is to last index
Array of Client Authentication methods supported by this OP's Introspection Endpoint. If no configuration value is provided the same values as for tokenEndpointAuthMethods will be used. Supported values list is the same as for tokenEndpointAuthMethods.
default value:
[
'none',
'client_secret_basic',
'client_secret_jwt',
'client_secret_post',
'private_key_jwt'
]
Helper used by the OP to decide whether a refresh token will be issued or not
default value:
async issueRefreshToken(ctx, client, code) {
return client.grantTypes.includes('refresh_token') && code.scopes.has('offline_access');
}
(Click to expand) To always issue a refresh tokens ...
...if a client has the grant whitelisted and scope includes offline_access or the client is a public web client doing code flow. Configure issueRefreshToken
like so
async issueRefreshToken(ctx, client, code) {
if (!client.grantTypes.includes('refresh_token')) {
return false;
}
return code.scopes.has('offline_access') || (client.applicationType === 'web' && client.tokenEndpointAuthMethod === 'none');
}
HTML source rendered when session management feature renders a confirmation prompt for the User-Agent.
default value:
async logoutSource(ctx, form) {
// @param ctx - koa request context
// @param form - form source (id="op.logoutForm") to be embedded in the page and submitted by
// the End-User
ctx.body = `<!DOCTYPE html>
<head>
<title>Logout Request</title>
<style>/* css and html classes omitted for brevity, see lib/helpers/defaults.js */</style>
</head>
<body>
<div>
<h1>Do you want to sign-out from ${ctx.host}?</h1>
${form}
<button autofocus type="submit" form="op.logoutForm" value="yes" name="logout">Yes, sign me out</button>
<button type="submit" form="op.logoutForm">No, stay signed in</button>
</div>
</body>
</html>`;
}
Function used by the OP when resolving pairwise ID Token and Userinfo sub claim values. See Core 1.0
recommendation: Since this might be called several times in one request with the same arguments consider using memoization or otherwise caching the result based on account and client ids.
default value:
async pairwiseIdentifier(ctx, accountId, client) {
return crypto.createHash('sha256')
.update(client.sectorIdentifier)
.update(accountId)
.update(os.hostname()) // put your own unique salt here, or implement other mechanism
.digest('hex');
}
fine-tune the supported code challenge methods. Supported values are
S256
plain
default value:
[
'S256'
]
HTML source rendered when session management feature concludes a logout but there was no post_logout_redirect_uri
provided by the client.
default value:
async postLogoutSuccessSource(ctx) {
// @param ctx - koa request context
const {
clientId, clientName, clientUri, initiateLoginUri, logoUri, policyUri, tosUri,
} = ctx.oidc.client || {}; // client is defined if the user chose to stay logged in with the OP
const display = clientName || clientId;
ctx.body = `<!DOCTYPE html>
<head>
<title>Sign-out Success</title>
<style>/* css and html classes omitted for brevity, see lib/helpers/defaults.js */</style>
</head>
<body>
<div>
<h1>Sign-out Success</h1>
<p>Your sign-out ${display ? `with ${display}` : ''} was successful.</p>
</div>
</body>
</html>`;
}
Helper used by the OP to present errors to the User-Agent
default value:
async renderError(ctx, out, error) {
ctx.type = 'html';
ctx.body = `<!DOCTYPE html>
<head>
<title>oops! something went wrong</title>
<style>/* css and html classes omitted for brevity, see lib/helpers/defaults.js */</style>
</head>
<body>
<div>
<h1>oops! something went wrong</h1>
${Object.entries(out).map(([key, value]) => `<pre><strong>${key}</strong>: ${value}</pre>`).join('')}
</div>
</body>
</html>`;
}
Array of response_type values that OP supports
default value:
[
'code id_token token',
'code id_token',
'code token',
'code',
'id_token token',
'id_token',
'none'
]
(Click to expand) Supported values list
These are values defined in Core 1.0 and OAuth 2.0 Multiple Response Type Encoding Practices
[
'code',
'id_token', 'id_token token',
'code id_token', 'code token', 'code id_token token',
'none',
]
Array of Client Authentication methods supported by this OP's Revocation Endpoint. If no configuration value is provided the same values as for tokenEndpointAuthMethods will be used. Supported values list is the same as for tokenEndpointAuthMethods.
default value:
[
'none',
'client_secret_basic',
'client_secret_jwt',
'client_secret_post',
'private_key_jwt'
]
Configures if and how the OP rotates refresh tokens after they are used. Supported values are
false
refresh tokens are not rotated and their initial expiration date is finaltrue
refresh tokens are rotated when used, current token is marked as consumed and new one is issued with new TTL, when a consumed refresh token is encountered an error is returned instead and the whole token chain (grant) is revoked- function returning true/false, true when rotation should occur, false when it shouldn't
default value:
true
(Click to expand) function use
async function rotateRefreshToken(ctx) {
// e.g.
// return refreshTokenCloseToExpiration(ctx.oidc.entities.RefreshToken);
// or
// return refreshTokenRecentlyRotated(ctx.oidc.entities.RefreshToken);
// or
// return customClientBasedPolicy(ctx.oidc.entities.Client);
}
Routing values used by the OP. Only provide routes starting with "/"
default value:
{
authorization: '/auth',
certificates: '/certs',
check_session: '/session/check',
code_verification: '/device',
device_authorization: '/device/auth',
end_session: '/session/end',
introspection: '/token/introspection',
registration: '/reg',
revocation: '/token/revocation',
token: '/token',
userinfo: '/me'
}
Array of the scope values that the OP supports
default value:
[
'openid',
'offline_access'
]
Array of the Subject Identifier types that this OP supports. When only pairwise
is supported it becomes the default subject_type
client metadata value. Valid types are
public
pairwise
default value:
[
'public'
]
Array of Client Authentication methods supported by this OP's Token Endpoint
default value:
[
'none',
'client_secret_basic',
'client_secret_jwt',
'client_secret_post',
'private_key_jwt'
]
(Click to expand) Supported values list
[
'none',
'client_secret_basic', 'client_secret_post',
'client_secret_jwt', 'private_key_jwt',
'tls_client_auth', 'self_signed_tls_client_auth',
]
(Click to expand) Setting up the environment for tls_client_auth and self_signed_tls_client_auth
To enable mutual TLS based authentication methods the provider expects your TLS-offloading proxy to handle the client certificate validation, parsing, handling, etc. Once set up you are expected to forward x-ssl-client-verify
, x-ssl-client-s-dn
and x-ssl-client-cert
headers with variable values set by this proxy. An important aspect is to sanitize the inbound request headers at the proxy.
The most common openssl based proxies are Apache and NGINX, with those you're looking to use
SSLVerifyClient
(Apache) / ssl_verify_client
(NGINX) with the appropriate configuration value that matches your setup requirements.
SSLCACertificateFile
or SSLCACertificatePath
(Apache) / ssl_client_certificate
(NGINX) with the values pointing to your accepted CA Certificates.
Set the proxy request headers with variables set as a result of enabling mutual TLS
# NGINX
proxy_set_header x-ssl-client-cert $ssl_client_cert;
proxy_set_header x-ssl-client-verify $ssl_client_verify;
proxy_set_header x-ssl-client-s-dn $ssl_client_s_dn;
# Apache
RequestHeader set x-ssl-client-cert ""
RequestHeader set x-ssl-client-cert "%{SSL_CLIENT_CERT}s"
RequestHeader set x-ssl-client-verify ""
RequestHeader set x-ssl-client-verify "%{SSL_CLIENT_VERIFY}s"
RequestHeader set x-ssl-client-s-dn ""
RequestHeader set x-ssl-client-s-dn "%{SSL_CLIENT_S_DN}s"
You should also consider hosting the endpoints supporting client authentication, on a separate host name or port in order to prevent unintended impact on the TLS behaviour of your other endpoints, e.g. Discovery or the authorization endpoint, by updating the discovery response to add draft-ietf-oauth-mtls-12 specified mtls_endpoint_aliases
.
provider.use(async (ctx, next) => {
await next();
if (ctx.oidc.route === 'discovery') {
ctx.body.mtls_endpoint_aliases = {};
const endpointAuthMethodKeys = [
'token_endpoint_auth_methods_supported',
'introspection_endpoint_auth_methods_supported',
'revocation_endpoint_auth_methods_supported',
];
// splits `*_endpoint_auth_methods_supported` into two namespaces (mutual-TLS and regular);
endpointAuthMethodKeys.forEach((key) => {
if (ctx.body[key]) {
ctx.body.mtls_endpoint_aliases[key] = ctx.body[key].filter(k => k.endsWith('tls_client_auth'));
ctx.body[key] = ctx.body[key].filter(k => !ctx.body.mtls_endpoint_aliases[key].includes(k));
}
});
const mtlsEndpoints = [
'userinfo_endpoint',
'token_endpoint',
'introspection_endpoint',
'revocation_endpoint',
'device_authorization_endpoint',
];
// aliases endpoints accepting client certificates in `mtls_endpoint_aliases`
const mtlsOrigin = 'https://mtls.op.example.com';
mtlsEndpoints.forEach((key) => {
if (ctx.body[key]) {
ctx.body.mtls_endpoint_aliases[key] = `${mtlsOrigin}${url.parse(ctx.body[key]).pathname}`;
}
});
}
});
When doing that be sure to remove the client provided headers of the same name on the non-mutual TLS enabled host name / port in your proxy setup or block the routes for these there completely.
Expirations (in seconds, or dynamically returned value) for all token types
default value:
{
AccessToken: 3600,
AuthorizationCode: 600,
ClientCredentials: 600,
DeviceCode: 600,
IdToken: 3600,
RefreshToken: 1209600
}
(Click to expand) To resolve a ttl on runtime for each new token
Configure ttl
for a given token type with a function like so, this must return a value, not a Promise.
{
ttl: {
AccessToken(ctx, token, client) {
// return a Number (in seconds) for the given token (first argument), the associated client is
// passed as a second argument
// Tip: if the values are entirely client based memoize the results
return resolveTTLfor(token, client);
},
},
}
Fine-tune the algorithms your provider will support by declaring algorithm values for each respective JWA use
recommendation: Only allow JWA algs that are necessary. The current defaults are based on recommendations from the JWA specification + enables RSASSA-PSS based on current guidance in FAPI. "none" JWT algs are disabled by default but available if you need them.
JWA algorithms the provider supports to wrap keys for JWT Authorization response encryption
default value:
[
'A128KW',
'A256KW',
'ECDH-ES',
'ECDH-ES+A128KW',
'ECDH-ES+A256KW',
'RSA-OAEP'
]
(Click to expand) Supported values list
[
// asymmetric RSAES based
'RSA-OAEP', 'RSA1_5',
// asymmetric ECDH-ES based
'ECDH-ES', 'ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW',
// symmetric AES
'A128KW', 'A192KW', 'A256KW',
// symmetric AES GCM based
'A128GCMKW', 'A192GCMKW', 'A256GCMKW',
// symmetric PBES2 + AES
'PBES2-HS256+A128KW', 'PBES2-HS384+A192KW', 'PBES2-HS512+A256KW',
]
JWA algorithms the provider supports to encrypt JWT Authorization Responses with
default value:
[
'A128CBC-HS256',
'A128GCM',
'A256CBC-HS512',
'A256GCM'
]
(Click to expand) Supported values list
[
'A128CBC-HS256', 'A128GCM', 'A192CBC-HS384', 'A192GCM', 'A256CBC-HS512', 'A256GCM',
]
JWA algorithms the provider supports to sign JWT Authorization Responses with
default value:
[
'HS256',
'RS256',
'PS256',
'ES256',
'EdDSA'
]
(Click to expand) Supported values list
[
'HS256', 'HS384', 'HS512',
'RS256', 'RS384', 'RS512',
'PS256', 'PS384', 'PS512',
'ES256', 'ES384', 'ES512',
'EdDSA',
]
JWA algorithms the provider supports to wrap keys for ID Token encryption
default value:
[
'A128KW',
'A256KW',
'ECDH-ES',
'ECDH-ES+A128KW',
'ECDH-ES+A256KW',
'RSA-OAEP'
]
(Click to expand) Supported values list
[
// asymmetric RSAES based
'RSA-OAEP', 'RSA1_5',
// asymmetric ECDH-ES based
'ECDH-ES', 'ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW',
// symmetric AES
'A128KW', 'A192KW', 'A256KW',
// symmetric AES GCM based
'A128GCMKW', 'A192GCMKW', 'A256GCMKW',
// symmetric PBES2 + AES
'PBES2-HS256+A128KW', 'PBES2-HS384+A192KW', 'PBES2-HS512+A256KW',
]
JWA algorithms the provider supports to encrypt ID Tokens with
default value:
[
'A128CBC-HS256',
'A128GCM',
'A256CBC-HS512',
'A256GCM'
]
(Click to expand) Supported values list
[
'A128CBC-HS256', 'A128GCM', 'A192CBC-HS384', 'A192GCM', 'A256CBC-HS512', 'A256GCM',
]
JWA algorithms the provider supports to sign ID Tokens with
default value:
[
'HS256',
'RS256',
'PS256',
'ES256',
'EdDSA'
]
(Click to expand) Supported values list
[
'none',
'HS256', 'HS384', 'HS512',
'RS256', 'RS384', 'RS512',
'PS256', 'PS384', 'PS512',
'ES256', 'ES384', 'ES512',
'EdDSA',
]
JWA algorithms the provider supports to wrap keys for JWT Introspection response encryption
default value:
[
'A128KW',
'A256KW',
'ECDH-ES',
'ECDH-ES+A128KW',
'ECDH-ES+A256KW',
'RSA-OAEP'
]
(Click to expand) Supported values list
[
// asymmetric RSAES based
'RSA-OAEP', 'RSA1_5',
// asymmetric ECDH-ES based
'ECDH-ES', 'ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW',
// symmetric AES
'A128KW', 'A192KW', 'A256KW',
// symmetric AES GCM based
'A128GCMKW', 'A192GCMKW', 'A256GCMKW',
// symmetric PBES2 + AES
'PBES2-HS256+A128KW', 'PBES2-HS384+A192KW', 'PBES2-HS512+A256KW',
]
JWA algorithms the provider supports to encrypt JWT Introspection responses with
default value:
[
'A128CBC-HS256',
'A128GCM',
'A256CBC-HS512',
'A256GCM'
]
(Click to expand) Supported values list
[
'A128CBC-HS256', 'A128GCM', 'A192CBC-HS384', 'A192GCM', 'A256CBC-HS512', 'A256GCM',
]
JWA algorithms the provider supports on the introspection endpoint
default value:
[
'HS256',
'RS256',
'PS256',
'ES256',
'EdDSA'
]
(Click to expand) Supported values list
[
'HS256', 'HS384', 'HS512',
'RS256', 'RS384', 'RS512',
'PS256', 'PS384', 'PS512',
'ES256', 'ES384', 'ES512',
'EdDSA',
]
JWA algorithms the provider supports to sign JWT Introspection responses with
default value:
[
'HS256',
'RS256',
'PS256',
'ES256',
'EdDSA'
]
(Click to expand) Supported values list
[
'none',
'HS256', 'HS384', 'HS512',
'RS256', 'RS384', 'RS512',
'PS256', 'PS384', 'PS512',
'ES256', 'ES384', 'ES512',
'EdDSA',
]
JWA algorithms the provider supports to receive encrypted Request Object keys wrapped with
default value:
[
'A128KW',
'A256KW',
'ECDH-ES',
'ECDH-ES+A128KW',
'ECDH-ES+A256KW',
'RSA-OAEP'
]
(Click to expand) Supported values list
[
// asymmetric RSAES based
'RSA-OAEP', 'RSA1_5',
// asymmetric ECDH-ES based
'ECDH-ES', 'ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW',
// symmetric AES
'A128KW', 'A192KW', 'A256KW',
// symmetric AES GCM based
'A128GCMKW', 'A192GCMKW', 'A256GCMKW',
// symmetric PBES2 + AES
'PBES2-HS256+A128KW', 'PBES2-HS384+A192KW', 'PBES2-HS512+A256KW',
]
JWA algorithms the provider supports decrypt Request Objects with encryption
default value:
[
'A128CBC-HS256',
'A128GCM',
'A256CBC-HS512',
'A256GCM'
]
(Click to expand) Supported values list
[
'A128CBC-HS256', 'A128GCM', 'A192CBC-HS384', 'A192GCM', 'A256CBC-HS512', 'A256GCM',
]
JWA algorithms the provider supports to receive Request Objects with
default value:
[
'HS256',
'RS256',
'PS256',
'ES256',
'EdDSA'
]
(Click to expand) Supported values list
[
'none',
'HS256', 'HS384', 'HS512',
'RS256', 'RS384', 'RS512',
'PS256', 'PS384', 'PS512',
'ES256', 'ES384', 'ES512',
'EdDSA',
]
JWA algorithms the provider supports on the revocation endpoint
default value:
[
'HS256',
'RS256',
'PS256',
'ES256',
'EdDSA'
]
(Click to expand) Supported values list
[
'HS256', 'HS384', 'HS512',
'RS256', 'RS384', 'RS512',
'PS256', 'PS384', 'PS512',
'ES256', 'ES384', 'ES512',
'EdDSA',
]
JWA algorithms the provider supports on the token endpoint
default value:
[
'HS256',
'RS256',
'PS256',
'ES256',
'EdDSA'
]
(Click to expand) Supported values list
[
'HS256', 'HS384', 'HS512',
'RS256', 'RS384', 'RS512',
'PS256', 'PS384', 'PS512',
'ES256', 'ES384', 'ES512',
'EdDSA',
]
JWA algorithms the provider supports to wrap keys for UserInfo Response encryption
default value:
[
'A128KW',
'A256KW',
'ECDH-ES',
'ECDH-ES+A128KW',
'ECDH-ES+A256KW',
'RSA-OAEP'
]
(Click to expand) Supported values list
[
// asymmetric RSAES based
'RSA-OAEP', 'RSA1_5',
// asymmetric ECDH-ES based
'ECDH-ES', 'ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW',
// symmetric AES
'A128KW', 'A192KW', 'A256KW',
// symmetric AES GCM based
'A128GCMKW', 'A192GCMKW', 'A256GCMKW',
// symmetric PBES2 + AES
'PBES2-HS256+A128KW', 'PBES2-HS384+A192KW', 'PBES2-HS512+A256KW',
]
JWA algorithms the provider supports to encrypt UserInfo responses with
default value:
[
'A128CBC-HS256',
'A128GCM',
'A256CBC-HS512',
'A256GCM'
]
(Click to expand) Supported values list
[
'A128CBC-HS256', 'A128GCM', 'A192CBC-HS384', 'A192GCM', 'A256CBC-HS512', 'A256GCM',
]
JWA algorithms the provider supports to sign UserInfo responses with
default value:
[
'HS256',
'RS256',
'PS256',
'ES256',
'EdDSA'
]
(Click to expand) Supported values list
[
'none',
'HS256', 'HS384', 'HS512',
'RS256', 'RS384', 'RS512',
'PS256', 'PS384', 'PS512',
'ES256', 'ES384', 'ES512',
'EdDSA',
]