Skip to content

Latest commit

 

History

History
666 lines (542 loc) · 25.6 KB

configuration.md

File metadata and controls

666 lines (542 loc) · 25.6 KB

Configuration

oidc-provider allows to be extended and configured in various ways to fit a variety of uses. You SHOULD tell your instance how to find your user accounts, where to store and retrieve persisted data from and where your user-interaction happens. The example application is a good starting point to get an idea of what you should provide.

Table of Contents

Default configuration values

Default values are available for all configuration options. Default configuration provides details on what the options mean and what part of the OP they affect.

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 and this is set to the request context.

const oidc = new Provider('http://localhost:3000', {
  findById: function (id) {
    return Promise.resolve({
      accountId: id,
      claims() { return { sub: id }; },
    });
  }
});

Note: the findById method needs to be yieldable, returning a Promise is recommended.
Tip: check how the example deals with this.

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 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', {
  discovery: {
    claim_types_supported: ['normal', 'aggregated', 'distributed']
  }
});

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.

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

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 oidc = new Provider('http://localhost:3000');
const clients = [
  {
    token_endpoint_auth_method: 'none',
    client_id: 'mywebsite',
    grant_types: ['implicit'],
    response_types: ['id_token'],
    redirect_uris: ['https://client.example.com/cb'],
  },
  {
    // ...
  },
];

oidc.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 and Token Integrity

See Certificates, Keystores, Token Integrity.

Configuring available claims

oidc-provider pushes by default acr, auth_time, iss, sub claims to the id token and userinfo. The claims configuration parameter can be used to define which claims fall under which 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', {
  claims: {
    [scope name]: ['claim name', 'claim name'],
    // or
    [scope name]: {
      [claim name]: null,
    },
    // or (for standalone claims)
    [standalone claim name]: null
  }
});

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

new Provider('http://localhost:3000', {
  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 extend or reduce the default scope names that are available. This list is extended by all scope names detected in the claims parameter as well. The parameter accepts an array of scope names.

Persistance

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 for 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 an adapter constructor for oidc-provider to use. This constructor will be called for every model is accessed the first time it is needed.

const MyAdapter = require('./my_adapter');
const oidc = new Provider('http://localhost:3000', {
  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 test [redis,mongodb] you can use to check your own implementation.

Interaction

Since oidc-provider only comes with feature-less views and interaction handlers it's up to you to fill those in, here's 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 create a _grant cookie that you can read from your interaction 'app'.

This cookie contains (serialized as JSON):

  • details of the interaction that is required
  • all authorization request parameters
  • 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 dumped in a signed _grant_result cookie.

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

//   with express
expressApp.get('/interaction/:grant', (req, res) => {
  const details = provider.interactionDetails(req);
  // ...
});

//   with koa
router.get('/interaction/:grant', function* (next) {
  const details = provider.interactionDetails(this.req);
  // ...
});

Provider#interactionFinished

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

//   with koa
router.post('/interaction/:grant', function* (next) {
  provider.interactionFinished(this.req, this.res, results); // result object below
  // ...
});

// results 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: {
    // use the scope property if you wish to remove/add scopes from the request, otherwise don't
    // include it use when i.e. offline_access was not given, or user declined to provide address
    scope: 'space separated list of scopes',
  },
  ['custom prompt name resolved']: {},
}

Enable/Disable optional oidc-provider features

There are many features defined in OIDC which are optional and can be omitted to keep your deployment compact. The feature flags with their default values are

feature flag enabled by default?
devInteractions yes (!!!)
backchannelLogout no
claimsParameter no
clientCredentials no
discovery yes
encryption no
introspection no
alwaysIssueRefresh no
registration no
registrationManagement no
request no
requestUri no
revocation no
oauthNativeApps no
sessionManagement no
pkce yes

Development quick-start interactions
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.

const configuration = { features: { devInteractions: Boolean[true] } };

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

const configuration = { features: { discovery: Boolean[true] } };

Authorization claims parameter
Enables the use and validations of claims parameter as described in Core 1.0 and sets the discovery endpoint property claims_parameter_supported to true.

const configuration = { features: { claimsParameter: Boolean[false] } };

Token endpoint client_credentials grant
Enables grant_type=client_credentials to be used on the token endpoint. Note: client still has to be allowed this grant.
Hint: allowing this grant together with token introspection and revocation is an easy and elegant way to allow authorized access to some less sensitive backend actions.

const configuration = { features: { clientCredentials: Boolean[false] } };

Encryption features
Enables clients to receive encrypted userinfo responses, encrypted ID Tokens and to send encrypted request parameters to authorization.

const configuration = { features: { encryption: Boolean[false] } };

Offline access - Refresh Tokens
The use of Refresh Tokens (offline access) as described in Core 1.0 Offline Access does not require any feature flag as Refresh Tokens will be issued by the authorization_code grant automatically in case the authentication request included offline_access scope and consent prompt and the client in question has the refresh_token grant configured.

Refresh Tokens beyond the scope

The use of Refresh Tokens is not exclusive to the offline_access use case. The Authorization Server MAY grant Refresh Tokens in other contexts that are beyond the scope of this specification.

Provide alwaysIssueRefresh feature flag to have your provider instance issue Refresh Tokens even if offline_access scope is not requested. The client still has to have refresh_token grant configured, else no Refresh Token will be issued since the client couldn't finish the grant anyway.

const configuration = { features: { alwaysIssueRefresh: Boolean[false] } };

Authorization request parameter
Enables the use and validations of request parameter as described in Core 1.0 and sets the discovery endpoint property request_parameter_supported to true.

const configuration = { features: { request: Boolean[false] } };

Authorization request_uri parameter
Enables the use and validations of request_uri parameter as described in Core 1.0 and sets the discovery endpoint property request_uri_parameter_supported to true.

const configuration = { features: { requestUri: Boolean[false] } };

To also enable require_request_uri_registration configure requestUri as an object like so:

const configuration = { features: { requestUri: { requireRequestUriRegistration: true } } };

Introspection endpoint
Enables the use of Introspection endpoint as described in RFC7662 for tokens of type AccessToken, ClientCredentials and RefreshToken. When enabled the token_introspection_endpoint property of the discovery endpoint is published, otherwise the property is not sent. The use of this endpoint is covered by the same authz mechanism as the regular token endpoint.

const configuration = { features: { introspection: Boolean[false] } };

This feature is a recommended way for Resource Servers to validate presented Bearer tokens, since the token endpoint access must be authorized it is recommended to setup a client for the RS to use. This client should be unusable for standard authorization flow, to set up such a client provide grant_types, response_types and redirect_uris as empty arrays.

Revocation endpoint
Enables the use of Revocation endpoint as described in RFC7009 for tokens of type AccessToken, ClientCredentials and RefreshToken. When enabled the token_revocation_endpoint property of the discovery endpoint is published, otherwise the property is not sent. The use of this endpoint is covered by the same authz mechanism as the regular token endpoint.

const configuration = { features: { revocation: Boolean[false] } };

OAuth 2.0 Native Apps Best Current Practice Changes redirect_uris validations for clients with application_type native to those defined in OAuth 2.0 for Native Apps.

const configuration = { features: { oauthNativeApps: Boolean[false] } };

Session management features
Enables features described in Session Management 1.0 - draft 28.

const configuration = { features: { sessionManagement: Boolean[false] } };

To disable removing frame-ancestors from Content-Security-Policy and X-Frame-Options in check_session_iframe calls because you know what you're doing with them, set:

const configuration = { features: { sessionManagement: { keepHeaders: true } } };

Back-Channel Logout features
Enables features described in Back-Channel Logout 1.0 - draft 04.

const configuration = { features: { sessionManagement: true, backchannelLogout: Boolean[false] } };

Dynamic registration features
Enables features described in Dynamic Client Registration 1.0.

const configuration = { features: { registration: Boolean[false] } };

To provide your own factory to get a new clientId:

const configuration = { features: { registration: { idFactory: () => randomValue() } } };

To enable a fixed Initial Access Token for the registration POST call configure registration to be an object like so:

const configuration = { features: { registration: { initialAccessToken: 'tokenValue' } } };

To enable a Initial Access Token lookup from your storage (via an Adapter of course) configure registration to be an object like so:

const configuration = { features: { registration: { initialAccessToken: true } } };

// adding a token and retrieving it's value
new (provider.InitialAccessToken)({}).save().then(console.log);

Dynamic registration management features
Enables Update and Delete features described in OAuth 2.0 Dynamic Client Registration Management Protocol.

const configuration = { features: { registration: true, registrationManagement: Boolean[false] } };

To have your provider discard the used and issue new RegistrationAccessToken with a successful update configure registrationManagement as an object like so:

const configuration = { features: { ..., registrationManagement: { rotateRegistrationAccessToken: true } } };

PKCE
Enables RFC7636 - Proof Key for Code Exchange by OAuth Public Clients

const configuration = { features: { pkce: Boolean[true] } };

To have native clients using code or hybrid flow forced to use pkce configure pkce as an object like so:

const configuration = { features: { pkce: { forcedForNative: true } } };

To allow native clients using that use pkce to skip token endpoint auth configure pkce as an object like so:

const configuration = { features: { pkce: { skipClientAuth: true } } };

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. You can check the standard grant factories here.

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

provider.registerGrantType('password', function passwordGrantTypeFactory(providerInstance) {
  return function * passwordGrantType(next) {
    if (this.oidc.params.username === 'foo' && this.oidc.params.password === 'bar') {
      const AccessToken = providerInstance.AccessToken;
      const at = new AccessToken({
        accountId: 'foo',
        clientId: this.oidc.client.clientId,
        grantId: this.oidc.uuid,
      });

      const accessToken = yield at.save();
      const tokenType = 'Bearer';
      const expiresIn = AccessToken.expiresIn;

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

    yield next;
  };
}, parameters);

Tip: you are able to modify the implemented grant type behavior like this.

Extending Authorization with Custom Parameters

You can extend the whitelisted parameters of authorization/authentication endpoint beyond the defaults. These will be available in ctx.oidc.params as well as passed via the _grant cookie to the interaction.

const oidc = new Provider('http://localhost:3000', {
  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', {
  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', {
  routes: {
    authorization: '/authz',
    certificates: '/jwks'
  }
});

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

Changing HTTP Request Defaults

On four occasions the OIDC Provider needs to venture out to he 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 got for http requests with the following default request options

const DEFAULT_HTTP_OPTIONS = {
  followRedirect: false,
  headers: { 'User-Agent': `${pkg.name}/${pkg.version} (${this.issuer}; ${pkg.homepage})` },
  retries: 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 overwrite the default ['0', '1', '2'], passing an empty array will disable the acr claim completely.

Mounting oidc-provider

The following snippets show how a provider instance can be mounted to existing applications with a path prefix. As shown it is recommended to rewrite the well-known uri calls so that they get handled by the provider.

to an express application

const rewrite = require('express-urlrewrite');
const prefix = '/op'
expressApp.use(rewrite('/.well-known/*', `${prefix}/.well-known/$1`));
expressApp.use(prefix, oidc.callback);

to a koa application

const rewrite = require('koa-rewrite');
const mount = require('koa-mount');
const prefix = '/op'
koaApp.use(rewrite('/.well-known/*', `${prefix}/.well-known/$1`));
koaApp.use(mount(prefix, oidc.app));

Trusting ssl offloading proxies

Having a TLS offloading proxy in front of node.js running oidc-provider is the norm. As with any express/koa application you have to tell your app to trust x-forwarded-proto and x-forwarded-for headers commonly set by those proxies to let the downstream application know of the original protocol and ip.

Depending on your setup you should do the following

setup example
standalone oidc-provider provider.app.proxy = true;
oidc-provider mounted to a koa app yourKoaApp.proxy = true
oidc-provider mounted to an express app provider.app.proxy = true;