Skip to content
This repository has been archived by the owner on Nov 21, 2023. It is now read-only.

Latest commit

 

History

History
2017 lines (1541 loc) · 61 KB

configuration.md

File metadata and controls

2017 lines (1541 loc) · 61 KB

Configuration

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.

Table of Contents

Basic configuration example

const Provider = require('oidc-provider');
const configuration = {
  // ... see the available options in Configuration options section
  features: {
    discovery: true,
    registration: { initialAccessToken: true },
  },
  format: { default: 'opaque' },
  // ...
};
const 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);

let server;
(async () => {
  await oidc.initialize({ clients });
  // 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
  server = oidc.listen(3000, () => {
    console.log('oidc-provider listening on port 3000, check http://localhost:3000/.well-known/openid-configuration');
  });
})().catch((err) => {
  if (server && server.listening) server.close();
  console.error(err);
  process.exitCode = 1;
});

Default configuration values

Default values are available for all configuration options. Available in code as well as in this document.

Accounts

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', {
  formats: { default: 'opaque' },
  async findById(ctx, id) {
    return {
      accountId: id,
      async claims(use, scope) { return { sub: id }; },
    };
  }
});

Clients

Clients can be passed to your provider instance during the initialize call or left to be loaded via your provided Adapter. oidc-provider will use the adapter's find method when a non-cached client_id is encountered. If you only wish to support clients that are initialized and no dynamic registration then make it so that your adapter resolves client find calls with a falsy value. (e.g. return Promise.resolve()).

Available Client Metadata is validated as defined by the specifications. This list is extended by other adjacent-specification related properties such as introspection and revocation endpoint authentication, Session Management, Front and Back-Channel Logout, etc.

Note: each oidc-provider caches the clients once they are loaded. When your adapter-stored client configuration changes you should either reload your processes or trigger a cache clear (provider.Client.cacheClear() to clear the complete cache or provider.Client.cacheClear(id) to clear a specific client instance from cache).

via Provider interface
To add pre-established clients use the initialize method on a oidc-provider instance. This accepts a clients array with metadata objects and rejects when the client metadata would be invalid.

const provider = new Provider('http://localhost:3000', {
  formats: { default: 'opaque' },
});
const clients = [
  {
    token_endpoint_auth_method: 'none',
    client_id: 'mywebsite',
    grant_types: ['implicit'],
    response_types: ['id_token'],
    redirect_uris: ['https://client.example.com/cb'],
  },
  {
    // ...
  },
];

provider.initialize({ clients }).then(fulfillmentHandler, rejectionHandler);

via Adapter
Storing client metadata in your storage is recommended for distributed deployments. Also when you want to provide a client configuration GUI or plan on changing this data often. Clients get loaded ! and validated ! when they are first needed, any metadata validation error encountered during this first load will be thrown and handled like any other context specific errors.

Note: Make sure your adapter returns an object with the correct property value types as if they were submitted via dynamic registration.

Certificates

See Certificates.

Configuring available claims

The claims configuration parameter can be used to define which claims fall under what scope as well as to expose additional claims that are available to RPs via the claims authorization parameter. The configuration value uses the following scheme:

new Provider('http://localhost:3000', {
  formats: { default: 'opaque' },
  claims: {
    [scope name]: ['claim name', 'claim name'],
    // or
    [scope name]: {
      [claim name]: null,
    },
    // or (for standalone claims) - only requestable via claims parameter
    //   (when features.claimsParameter is true)
    [standalone claim name]: null
  }
});

To follow the Core-defined scope-to-claim mapping use:

new Provider('http://localhost:3000', {
  formats: { default: 'opaque' },
  claims: {
    address: ['address'],
    email: ['email', 'email_verified'],
    phone: ['phone_number', 'phone_number_verified'],
    profile: ['birthdate', 'family_name', 'gender', 'given_name', 'locale', 'middle_name', 'name',
      'nickname', 'picture', 'preferred_username', 'profile', 'updated_at', 'website', 'zoneinfo'],
  },
});

Configuring available scopes

Use the scopes configuration parameter to configure the scope values that you wish to support. This list is extended by all scope names detected in the claims parameter as well.

Use the dynamicScopes configuration parameter to configure dynamic scope values.

Persistence

The provided example and any new instance of oidc-provider will use the basic in-memory adapter for storing issued tokens, codes, user sessions and dynamically registered clients. 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. A static connect method is called if present during the provider.initialize() call.

const MyAdapter = require('./my_adapter');
const provider = new Provider('http://localhost:3000', {
  formats: { default: 'opaque' },
});
provider.initialize({
  adapter: MyAdapter,
});

The API oidc-provider expects is documented here. For reference see the memory adapter and redis or mongodb adapters. There's also a simple adapter conformance test that can be used to check your own adapter implementation, (with already written tests for the redis and the mongodb implementations).

Interaction

Since oidc-provider only comes with feature-less views and interaction handlers it's 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 uuid 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 uuid 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/:grant', async (req, res) => {
  const details = await provider.interactionDetails(req);
  // ...
});

// with koa
router.get('/interaction/:grant', async (ctx, next) => {
  const details = await provider.interactionDetails(ctx.req);
  // ...
});

#provider.interactionFinished(req, res, result)

// with express
expressApp.post('/interaction/:grant/login', async (req, res) => {
  return provider.interactionFinished(req, res, result); // result object below
});

// with koa
router.post('/interaction/:grant', 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
    ts: number, // unix timestamp of the authentication
  },

  // 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 interactionCheck 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/:grant/login', async (req, res) => {
  const redirectTo = await provider.interactionResult(req, res, result);

  res.send({ redirectTo });
});

// with koa
router.post('/interaction/:grant', 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/:grant/login', async (req, res) => {
  await provider.setProviderSession(req, res, { account: 'accountId' });
  // ...
});

// with koa
router.post('/interaction/:grant/login', async (ctx, next) => {
  await provider.setProviderSession(ctx.req, ctx.res, { account: 'accountId' });
  // ...
});

Custom Grant Types

oidc-provider comes with the basic grants implemented, but you can register your own grant types, for example to implement a password grant type or OAuth 2.0 Token Exchange. You can check the standard grant factories here.

const parameters = ['username', 'password'];

// For OAuth 2.0 Token Exchange you can specify allowedDuplicateParameters as ['audience', 'resource']
const allowedDuplicateParameters = [];

provider.registerGrantType('password', function passwordGrantTypeFactory(providerInstance) {
  return async function passwordGrantType(ctx, next) {
    let account;
    if ((account = await Account.authenticate(ctx.oidc.params.username, ctx.oidc.params.password))) {
      const AccessToken = providerInstance.AccessToken;
      const at = new AccessToken({
				gty: 'password',
        accountId: account.id,
        clientId: ctx.oidc.client.clientId,
        grantId: ctx.oidc.uuid,
      });

      const accessToken = await at.save();
      const expiresIn = AccessToken.expiresIn;

      ctx.body = {
        access_token: accessToken,
        expires_in: expiresIn,
        token_type: 'Bearer',
      };
    } else {
      ctx.body = {
        error: 'invalid_grant',
        error_description: 'invalid credentials provided',
      };
      ctx.status = 400;
    }

    await next();
  };
}, parameters, allowedDuplicateParameters);

Extending Authorization with Custom Parameters

You can extend the whitelisted parameters of authorization endpoint beyond the defaults. These will be available in ctx.oidc.params as well as passed to the interaction session object for you to read.

const oidc = new Provider('http://localhost:3000', {
  formats: { default: 'opaque' },
  extraParams: ['utm_campaign', 'utm_medium', 'utm_source', 'utm_term'],
});

Extending Discovery with Custom Properties

You can extend the returned discovery properties beyond the defaults

const oidc = new Provider('http://localhost:3000', {
  formats: { default: 'opaque' },
  discovery: {
    service_documentation: 'http://server.example.com/connect/service_documentation.html',
    ui_locales_supported: ['en-US', 'en-GB', 'en-CA', 'fr-FR', 'fr-CA'],
    version: '3.1',
  }
});

Configuring Routes

You can change the default routes by providing a routes object to the oidc-provider constructor. See the specific routes in default configuration.

const oidc = new Provider('http://localhost:3000', {
  formats: { default: 'opaque' },
  routes: {
    authorization: '/authz',
    certificates: '/jwks.json',
  }
});

Fine-tuning supported algorithms

The lists of supported algorithms exposed via discovery and used when validating request objects and client metadata is a union of

  • all symmetrical algorithms where they apply
  • algorithms from the keystore you initialize the provider with

If you wish to tune the algorithms further you may do so via the unsupported configuration property.

HTTP Request Library / Proxy settings

By default oidc-provider uses the got module. Because of its lightweight nature of the provider will not use environment-defined http(s) proxies. In order to have them used you'll need to require and tell oidc-provider to use request instead.

# add request to your application package bundle
npm install request@^2.0.0 --save
// tell oidc-provider to use request instead of got
Provider.useRequest();

Changing HTTP Request Defaults

On four occasions the OIDC Provider needs to venture out to the world wide webs to fetch or post to external resources, those are

  • fetching an authorization request by request_uri reference
  • fetching and refreshing client's referenced asymmetric keys (jwks_uri client metadata)
  • validating pairwise client's relation to a sector (sector_identifier_uri client metadata)
  • posting to client's backchannel_logout_uri

oidc-provider uses these default options for http requests

const DEFAULT_HTTP_OPTIONS = {
  followRedirect: false,
  headers: { 'User-Agent': `${pkg.name}/${pkg.version} (${this.issuer})` },
  retry: 0,
  timeout: 1500,
};

Setting defaultHttpOptions on Provider instance merges your passed options with these defaults, for example you can add your own headers, change the user-agent used or change the timeout setting

provider.defaultHttpOptions = { timeout: 2500, headers: { 'X-Your-Header': '<whatever>' } };

Confirm your httpOptions by

console.log('httpOptions %j', provider.defaultHttpOptions);

Authentication Context Class Reference

Supply an array of string values to acrValues configuration option to set acr_values_supported. Passing an empty array disables the acr claim and removes acr_values_supported from discovery.

Registering module middlewares (helmet, ip-filters, rate-limiters, etc)

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());

Pre- and post-middlewares

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('middleware pre', 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`
   * `client_delete`
   * `client_update`
   * `code_verification`
   * `device_authorization`
   * `device_resume`
   * `end_session`
   * `introspection`
   * `registration`
   * `resume`
   * `revocation`
   * `token`
   * `userinfo`
   * `webfinger`
   * `check_session`
   * `check_session_origin`
   * `client`
   * `discovery`
   *
   * ctx.method === 'OPTIONS' is then useful for filtering out CORS Pre-flights
   */
   console.log('middleware post', ctx.method, ctx.oidc.route);
});

Mounting oidc-provider

The following snippets show how a provider instance can be mounted to existing applications with a path prefix.

to an express application

// assumes express ^4.0.0
const prefix = '/oidc';
expressApp.use(prefix, oidc.callback);

to a koa application

// assumes koa ^2.0.0
// assumes koa-router ^7.0.0
const mount = require('koa-mount');
const prefix = '/oidc';
koaApp.use(mount(prefix, oidc.app));

Trusting TLS offloading proxies

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;
}

Aggregated and Distributed claims

Returning aggregated and distributed claims is as easy as having your Account#claims method return the two necessary members _claim_sources and _claim_names with the [expected][aggregated-distributed-claims] properties. oidc-provider will include only the sources for claims that are part of the request scope, omitting the ones that the RP did not request and leaving out the entire _claim_sources and _claim_sources if they bear no requested claims.

Note: to make sure the RPs can expect these claims you should configure your discovery to return the respective claim types via the claim_types_supported property.

const oidc = new Provider('http://localhost:3000', {
  formats: { default: 'opaque' },
  discovery: {
    claim_types_supported: ['normal', 'aggregated', 'distributed']
  }
});

Configuration options

features

Enable/disable features.

default value (Click to expand)
{ devInteractions: true,
  discovery: true,
  requestUri: true,
  oauthNativeApps: true,
  pkce: true,
  alwaysIssueRefresh: false,
  backchannelLogout: false,
  claimsParameter: false,
  clientCredentials: false,
  conformIdTokenClaims: false,
  deviceCode: false,
  encryption: false,
  frontchannelLogout: false,
  introspection: false,
  jwtIntrospection: false,
  registration: false,
  registrationManagement: false,
  request: false,
  revocation: false,
  sessionManagement: false,
  webMessageResponseMode: false }

features.backchannelLogout

Back-Channel Logout 1.0 - draft 04

Enables Back-Channel Logout features.

default value:

false

features.claimsParameter

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:

false

features.clientCredentials

RFC6749 - Client Credentials

Enables grant_type=client_credentials to be used on the token endpoint.

default value:

false

features.devInteractions

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:

true

features.deviceCode

draft-ietf-oauth-device-flow-12 - Device Flow for Browserless and Input Constrained Devices

Enables Device Flow features

default value:

false

features.discovery

Discovery 1.0

Exposes /.well-known/webfinger and /.well-known/openid-configuration endpoints. Contents of the latter reflect your actual configuration, i.e. Available claims, features and so on.
WebFinger always returns positive results and links to this issuer, it is not resolving the resources in any way.

default value:

true

features.encryption

Enables encryption features such as receiving encrypted UserInfo responses, encrypted ID Tokens and allow receiving encrypted Request Objects.

default value:

false

features.frontchannelLogout

Front-Channel Logout 1.0 - draft 02

Enables Front-Channel Logout features

default value:

false

features.introspection

RFC7662 - OAuth 2.0 Token Introspection

Enables Token Introspection features

default value:

false

features.jwtIntrospection

draft-ietf-oauth-jwt-introspection-response-00 - JWT Response for OAuth Token Introspection

Enables JWT responses for Token Introspection features

default value:

false

features.oauthNativeApps

RFC8252 - OAuth 2.0 Native Apps Best Current Practice

Changes redirect_uris validations for clients with application_type native to those defined in the RFC. If PKCE is not enabled it will be force-enabled automatically.

default value:

true

features.pkce

RFC7636 - Proof Key for Code Exchange by OAuth Public Clients

Enables PKCE.

default value:

true
(Click to expand) To force native clients to use PKCE

Configure features.pkce with an object like so instead of a Boolean value.

{ forcedForNative: true }
(Click to expand) To fine-tune the supported code challenge methods

Configure features.pkce with an object like so instead of a Boolean value.

{ supportedMethods: ['plain', 'S256'] }

features.registration

Dynamic Client Registration 1.0

Enables Dynamic Client Registration, by default with no Initial Access Token.

default value:

false
(Click to expand) To enable a fixed Initial Access Token for the registration POST call

Configure features.registration to be an object like so:

{ initialAccessToken: 'tokenValue' }
(Click to expand) To provide your own client_id value generator:
{ idFactory: () => randomValue() }
(Click to expand) To provide your own client_secret value generator:
{ secretFactory: () => randomValue() }
(Click to expand) To enable a Initial Access Token lookup from your Adapter's store

Configure features.registration to be an object like so:

{ initialAccessToken: true }
(Click to expand) To add an Initial Access Token and retrive its value
new (provider.InitialAccessToken)({}).save().then(console.log);

features.registrationManagement

OAuth 2.0 Dynamic Client Registration Management Protocol

Enables Update and Delete features described in the RFC, by default with no rotating Registration Access Token.

default value:

false
(Click to expand) To have your provider rotate the Registration Access Token with a successful update

Configure features.registrationManagement as an object like so:

{ rotateRegistrationAccessToken: true }

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.

features.request

Core 1.0 - Passing a Request Object by Value

Enables the use and validations of request parameter

default value:

false

features.requestUri

Core 1.0 - Passing a Request Object by Reference

Enables the use and validations of request_uri parameter

default value:

true
(Click to expand) To disable request_uri pre-registration

Configure features.requestUri with an object like so instead of a Boolean value.

{ requireRequestUriRegistration: false }

features.revocation

RFC7009 - OAuth 2.0 Token Revocation

Enables Token Revocation

default value:

false

features.sessionManagement

Session Management 1.0 - draft 28

Enables Session Management features.

default value:

false
(Click to expand) [RECOMMENDED] To avoid endless "changed" events when Third-Party Cookies are disabled

The User-Agent must allow access to the provider cookies from a third-party context when the OP frame is embedded. Oidc-provider checks if this is enabled using a CDN hosted iframe. It is recommended to host these helper pages on your own (on a different domain from the one you host oidc-provider on). Once hosted, set the cookies.thirdPartyCheckUrl to an absolute URL for the start page. See this for more info. Note: This is still just a best-effort solution and is in no way bulletproof. Currently there's no better way to check if access to third party cookies has been blocked or the cookies are just missing. (ITP2.0 Storage Access API is also not an option)

(Click to expand) To disable removing frame-ancestors from Content-Security-Policy and X-Frame-Options

Only do 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. Configure features.sessionManagement as an object like so:

{ keepHeaders: true }

features.webMessageResponseMode

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:

false

acrValues

Array of strings, the Authentication Context Class References that OP supports.

affects: discovery, ID Token acr claim values

default value:

[]

audiences

Helper used by the OP to push additional audiences to issued ID, Access and ClientCredentials Tokens as well as other signed responses. The return value should either be falsy to omit adding additional audiences or an array of strings to push.

affects: ID Token audiences, access token audiences, client credential audiences, signed UserInfo audiences

default value (Click to expand)
async audiences(ctx, sub, token, use, scope) {
  // @param ctx   - koa request context
  // @param sub   - account identifier (subject)
  // @param token - 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
  // @param use   - can be one of "id_token", "userinfo", "access_token" depending on where the
  //   specific audiences are intended to be put in
  // @param scope - scope from either the request or related token
  return undefined;
}

claims

List of the Claim Names of the Claims that the OpenID Provider MAY be able to supply values for.

affects: discovery, ID Token claim names, Userinfo claim names

default value:

{ acr: null,
  sid: null,
  auth_time: null,
  iss: null,
  openid: [ 'sub' ] }

clientCacheDuration

A Number value (in seconds) describing how long a dynamically loaded client should remain cached.

affects: adapter-backed client cache duration
recommendation: do not set to a low value or completely disable this, client properties are validated upon loading up and this may be potentially an expensive operation, sometimes even requesting resources from the network (i.e. client jwks_uri, sector_identifier_uri etc).

default value:

Infinity

clockTolerance

A Number value (in seconds) describing the allowed system clock skew

affects: JWT (ID token, client assertion) and Token expiration validations
recommendation: Set to a reasonable value (60) to cover server-side client and oidc-provider server clock skew

default value:

0

cookies

Options for the cookie module used by the OP to keep track of various User-Agent states.

affects: User-Agent sessions, passing of authorization details to interaction

cookies.keys

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:

[]

cookies.long

Options for long-term cookies

affects: User-Agent session reference, Session Management states
recommendation: set cookies.keys and cookies.long.signed = true

default value:

{ secure: undefined,
  signed: undefined,
  httpOnly: true,
  maxAge: 1209600000 }

cookies.names

Cookie names used by the OP to store and transfer various states.

affects: User-Agent session, Session Management states and interaction cookie names

default value:

{ session: '_session',
  interaction: '_grant',
  resume: '_grant',
  state: '_state' }

cookies.short

Options for short-term cookies

affects: passing of authorization details to interaction
recommendation: set cookies.keys and cookies.short.signed = true

default value:

{ secure: undefined,
  signed: undefined,
  httpOnly: true,
  maxAge: 600000 }

cookies.thirdPartyCheckUrl

URL for 3rd party cookies support check helper

affects: sessionManagement feature

default value:

'https://cdn.rawgit.com/panva/3rdpartycookiecheck/92fead3f/start.html'

deviceCodeSuccess

HTML source rendered when device code feature renders a success page for the User-Agent.

affects: device code success page

default value (Click to expand)
async deviceCodeSuccess(ctx) {
  // @param ctx - koa request context
  const {
    clientId, clientName, clientUri, initiateLoginUri, logoUri, policyUri, tosUri,
  } = ctx.oidc.client;
  ctx.body = `<!DOCTYPE html>
<head>
<title>Sign-in Success</title>
<style>/* css and html classes omitted for brevity, see lib/helpers/defaults.js */</style>
</head>
<body>
<div>
  <h1>Sign-in Success</h1>
  <p>Your login ${clientName ? `with ${clientName}` : ''} was successful, you can now close this page.</p>
</div>
</body>
</html>`;
}

discovery

Pass additional properties to this object to extend the discovery document

affects: discovery

default value (Click to expand)
{ 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 }

dynamicScopes

List 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.

affects: discovery, authorization, ID Token claims, Userinfo claims

default value:

[]
(Click to expand) Example: To enable a dynamic scope values like `write:{hex id}` and `read:{hex id}`

Configure dynamicScopes like so:

[
  /^write:[a-fA-F0-9]{2,}$/,
  /^read:[a-fA-F0-9]{2,}$/,
]

extraClientMetadata

Allows for custom client metadata to be defined, validated, manipulated as well as for existing property validations to be extended

affects: clients, registration, registration management

extraClientMetadata.properties

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:

[]

extraClientMetadata.validator

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 (Click to expand)
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.
}

extraParams

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

affects: authorization, device_authorization, interaction

default value:

[]

findById

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

affects: authorization, authorization_code and refresh_token grants, ID Token claims

default value (Click to expand)
async findById(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 };
    },
  };
}

formats

This option allows to configure the token storage and value formats. The different values change how a token value is generated as well as what properties get sent to the adapter for storage. Three formats are defined, see the expected Adapter API for each format's specifics.

  • legacy is the current and default format until next major release. No changes in the format sent to adapter.
  • opaque formatted tokens have a different value then legacy and in addition store what was in legacy format encoded under payload as root properties, this makes analysing the data in your storage way easier
  • jwt formatted tokens are issued as JWTs and stored the same as opaque only with additional property jwt. The signing algorithm for these tokens uses the client's id_token_signed_response_alg value and falls back to RS256 for tokens with no relation to a client or when the client's alg is none

affects: properties passed to adapters for token types, issued token formats
recommendation: set default to opaque if you're still developing your application, legacy will not be the default in the major versions coming forward. It is not recommended to set jwt as default, if you need it, it's most likely just for Access Tokens.

default value (Click to expand)
{ default: 'legacy',
  AccessToken: undefined,
  AuthorizationCode: undefined,
  RefreshToken: undefined,
  DeviceCode: undefined,
  ClientCredentials: undefined,
  InitialAccessToken: undefined,
  RegistrationAccessToken: undefined }
(Click to expand) [RECOMMENDED] If you're starting from scratch

Do yourself a favour and disable the deprecated legacy format.

{ default: 'opaque' }
(Click to expand) To enable JWT Access Tokens

Configure formats:

{ default: 'opaque', AccessToken: 'jwt' }

frontchannelLogoutPendingSource

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.

affects: session management

default value (Click to expand)
async frontchannelLogoutPendingSource(ctx, frames, postLogoutRedirectUri, timeout) {
  ctx.body = `<!DOCTYPE html>
<head>
<title>Logout</title>
<style>/* css and html classes omitted for brevity, see lib/helpers/defaults.js */</style>
</head>
<body>
${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, ${timeout});
</script>
</body>
</html>`;
}

interactionCheck

Helper used by the OP as a final check whether the End-User should be sent to interaction or not, the default behavior is that every RP must be authorized per session and that native application clients always require End-User prompt to be confirmed. Return false if no interaction should be performed, return an object with relevant error, reason, etc. When interaction should be requested

affects: authorization interactions

default value (Click to expand)
async interactionCheck(ctx) {
  if (!ctx.oidc.session.sidFor(ctx.oidc.client.clientId)) {
    return {
      error: 'consent_required',
      error_description: 'client not authorized for End-User session yet',
      reason: 'client_not_authorized',
    };
  }
  if (
    ctx.oidc.client.applicationType === 'native'
    && ctx.oidc.params.response_type !== 'none'
    && !ctx.oidc.result) {
    return {
      error: 'interaction_required',
      error_description: 'native clients require End-User interaction',
      reason: 'native_client_prompt',
    };
  }
  const promptedScopes = ctx.oidc.session.promptedScopesFor(ctx.oidc.client.clientId);
  for (const scope of ctx.oidc.requestParamScopes) {
    if (!promptedScopes.has(scope)) {
      return {
        error: 'consent_required',
        error_description: 'requested scopes not granted by End-User',
        reason: 'scopes_missing',
      };
    }
  }
  const promptedClaims = ctx.oidc.session.promptedClaimsFor(ctx.oidc.client.clientId);
  for (const claim of ctx.oidc.requestParamClaims) {
    if (!promptedClaims.has(claim) && !['sub', 'sid', 'auth_time', 'acr', 'amr', 'iss'].includes(claim)) {
      return {
        error: 'consent_required',
        error_description: 'requested claims not granted by End-User',
        reason: 'claims_missing',
      };
    }
  }
  return false;
}

interactionUrl

Helper used by the OP to determine where to redirect User-Agent for necessary interaction, can return both absolute and relative urls

affects: authorization interactions

default value:

async interactionUrl(ctx, interaction) {
  return `/interaction/${ctx.oidc.uuid}`;
}

introspectionEndpointAuthMethods

List of Client Authentication methods supported by this OP's Introspection Endpoint

affects: discovery, client authentication for introspection, registration and registration management

default value:

[ 'none',
  'client_secret_basic',
  'client_secret_jwt',
  'client_secret_post',
  'private_key_jwt' ]

logoutSource

HTML source rendered when when session management feature renders a confirmation prompt for the User-Agent.

affects: session management

default value (Click to expand)
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>
  <script>
    function logout() {
      var form = document.getElementById('op.logoutForm');
      var input = document.createElement('input');
      input.type = 'hidden';
      input.name = 'logout';
      input.value = 'yes';
      form.appendChild(input);
      form.submit();
    }
    function rpLogoutOnly() {
      var form = document.getElementById('op.logoutForm');
      form.submit();
    }
  </script>
  ${form}
  <button onclick="logout()">Yes, sign me out</button>
  <button onclick="rpLogoutOnly()">No, stay signed in</button>
</div>
</body>
</html>`;
}

pairwiseSalt

Salt used by OP when resolving pairwise ID Token and Userinfo sub claim value

affects: ID Token and Userinfo sub claim values

default value:

''

postLogoutRedirectUri

URL to which the OP redirects the User-Agent when no post_logout_redirect_uri is provided by the RP

affects: session management

default value:

async postLogoutRedirectUri(ctx) {
  return ctx.origin;
}

prompts

List of the prompt values that the OpenID Provider MAY be able to resolve

affects: authorization

default value:

[ 'consent', 'login', 'none' ]

refreshTokenRotation

Configures if and how the OP rotates refresh tokens after they are used. Supported values are

  • none refresh tokens are not rotated and their initial expiration date is final
  • rotateAndConsume when 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

affects: refresh token rotation and adjacent revocation

default value:

'rotateAndConsume'

renderError

Helper used by the OP to present errors to the User-Agent

affects: presentation of errors encountered during End-User flows

default value (Click to expand)
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>`;
}

responseTypes

List of response_type values that OP supports

affects: authorization, discovery, registration, registration management

default value (Click to expand)
[ 'code id_token token',
  'code id_token',
  'code token',
  'code',
  'id_token token',
  'id_token',
  'none' ]

revocationEndpointAuthMethods

List of Client Authentication methods supported by this OP's Revocation Endpoint

affects: discovery, client authentication for revocation, registration and registration management

default value:

[ 'none',
  'client_secret_basic',
  'client_secret_jwt',
  'client_secret_post',
  'private_key_jwt' ]

routes

Routing values used by the OP. Only provide routes starting with "/"

affects: routing

default value (Click to expand)
{ authorization: '/auth',
  certificates: '/certs',
  check_session: '/session/check',
  device_authorization: '/device/auth',
  end_session: '/session/end',
  introspection: '/token/introspection',
  registration: '/reg',
  revocation: '/token/revocation',
  token: '/token',
  userinfo: '/me',
  code_verification: '/device' }

scopes

List of the scope values that the OP supports

affects: discovery, authorization, ID Token claims, Userinfo claims

default value:

[ 'openid', 'offline_access' ]

subjectTypes

List of the Subject Identifier types that this OP supports. Valid types are

  • public
  • pairwise

affects: discovery, registration, registration management, ID Token and Userinfo sub claim values

default value:

[ 'public' ]

tokenEndpointAuthMethods

List of Client Authentication methods supported by this OP's Token Endpoint

affects: discovery, client authentication for token endpoint, registration and registration management

default value:

[ 'none',
  'client_secret_basic',
  'client_secret_jwt',
  'client_secret_post',
  'private_key_jwt' ]

ttl

Expirations (in seconds) for all token types

affects: tokens

default value (Click to expand)
{ AccessToken: 3600,
  AuthorizationCode: 600,
  ClientCredentials: 600,
  DeviceCode: 600,
  IdToken: 3600,
  RefreshToken: 1209600 }

uniqueness

Function resolving whether a given value with expiration is presented first time

affects: client_secret_jwt and private_key_jwt client authentications
recommendation: configure this option to use a shared store if client_secret_jwt and private_key_jwt are used

default value:

async uniqueness(ctx, jti, expiresAt) {
  if (cache.get(jti)) return false;
  cache.set(jti, true, (expiresAt - epochTime()) * 1000);
  return true;
}

unsupported

Fine-tune the algorithms your provider should support by further omitting values from the respective discovery properties

affects: signing, encryption, discovery, client validation

default value (Click to expand)
{ idTokenEncryptionAlgValues: [],
  idTokenEncryptionEncValues: [],
  idTokenSigningAlgValues: [],
  requestObjectEncryptionAlgValues: [],
  requestObjectEncryptionEncValues: [],
  requestObjectSigningAlgValues: [],
  tokenEndpointAuthSigningAlgValues: [],
  introspectionEndpointAuthSigningAlgValues: [],
  revocationEndpointAuthSigningAlgValues: [],
  userinfoEncryptionAlgValues: [],
  userinfoEncryptionEncValues: [],
  userinfoSigningAlgValues: [],
  introspectionEncryptionAlgValues: [],
  introspectionEncryptionEncValues: [],
  introspectionSigningAlgValues: [] }

userCodeConfirmSource

HTML source rendered when device code feature renders an a confirmation prompt for ther User-Agent.

affects: device code authorization confirmation

default value (Click to expand)
async userCodeConfirmSource(ctx, form, client, deviceInfo) {
  // @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
  const {
    clientId, clientName, clientUri, logoUri, policyUri, tosUri,
  } = ctx.oidc.client;
  ctx.body = `<!DOCTYPE html>
<head>
<title>Device Login Confirmation</title>
<style>/* css and html classes omitted for brevity, see lib/helpers/defaults.js */</style>
</head>
<body>
<div>
  <h1>Confirm Device</h1>
  <p>
    You are about to authorize a <code>${clientName || clientId}</code> device client on IP <code>${deviceInfo.ip}</code>, identified by <code>${deviceInfo.userAgent}</code>
    <br/><br/>
    If you did not initiate this action and/or are unaware of such device in your possession please close this window.
  </p>
  ${form}
  <button autofocus type="submit" form="op.deviceConfirmForm">Continue</button>
  <div>
    <a href="">[ Cancel ]</a>
  </div>
</div>
</body>
</html>`;
}

userCodeInputSource

HTML source rendered when device code feature renders an input prompt for the User-Agent.

affects: device code input

default value (Click to expand)
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) {
    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>
<head>
<title>Sign-in</title>
<style>/* css and html classes omitted for brevity, see lib/helpers/defaults.js */</style>
</head>
<body>
<div>
  <h1>Sign-in</h1>
  ${msg}
  ${form}
  <button type="submit" form="op.deviceInputForm">Continue</button>
</div>
</body>
</html>`;
}