diff --git a/docs/README.md b/docs/README.md index 13c048b70..4443a2fc7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,16 +5,13 @@ will have to configure your instance with how to find your user accounts, where persisted data from and where your end-user interactions happen. The [example](/example) application is a good starting point to get an idea of what you should provide. -> ⚠️⚠️ This page now describes oidc-provider version v8.x documentation. See -[here](https://github.com/panva/node-oidc-provider/blob/v7.x/docs/README.md) for v7.x. - ## Sponsor Auth0 by Okta - + 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. [Create an Auth0 account; it's free!][sponsor-auth0]

@@ -45,24 +42,24 @@ If you or your company use this module, or you need help using/upgrading the mod - [Configuration options ❗](#configuration-options) - [FAQ ❗](#faq) - - ## Basic configuration example ```js -import Provider from 'oidc-provider'; +import Provider from 'oidc-provider' const configuration = { // ... see the available options in Configuration options section - clients: [{ - client_id: 'foo', - client_secret: 'bar', - redirect_uris: ['http://lvh.me:8080/cb'], - // + other client properties - }], + 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); +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() @@ -72,11 +69,12 @@ 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'); -}); + console.log( + 'oidc-provider listening on port 3000, check http://localhost:3000/.well-known/openid-configuration', + ) +}) ``` - ## Accounts This module needs to be able to find an account and once found the account needs to have an @@ -89,19 +87,21 @@ const oidc = new Provider('http://localhost:3000', { async findAccount(ctx, id) { return { accountId: id, - async claims(use, scope) { return { sub: id }; }, - }; - } -}); + async claims(use, scope) { + return { sub: id } + }, + } + }, +}) ``` - ## User flows + Since oidc-provider only comes with feature-less views and interaction handlers it is up to you to fill those in, here is how this module 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 the +user session, requested ACR not fulfilled, prompt requested, ...) it will resolve the [`interactions.url`](#interactionsurl) helper function and redirect the User-Agent to that URL. Before doing so it will save a short-lived "interaction session" and dump its identifier into a cookie scoped to the resolved interaction path. @@ -123,23 +123,24 @@ 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 [in-repo](/example) examples. - **`#provider.interactionDetails(req, res)`** + ```js // with express expressApp.get('/interaction/:uid', async (req, res) => { - const details = await provider.interactionDetails(req, res); + const details = await provider.interactionDetails(req, res) // ... -}); +}) // with koa router.get('/interaction/:uid', async (ctx, next) => { - const details = await provider.interactionDetails(ctx.req, ctx.res); + const details = await provider.interactionDetails(ctx.req, ctx.res) // ... -}); +}) ``` **`#provider.interactionFinished(req, res, result)`** + ```js // with express expressApp.post('/interaction/:uid/login', async (req, res) => { @@ -189,66 +190,75 @@ immediate http redirect. ```js // with express expressApp.post('/interaction/:uid/login', async (req, res) => { - const redirectTo = await provider.interactionResult(req, res, result); + const redirectTo = await provider.interactionResult(req, res, result) - res.send({ redirectTo }); -}); + res.send({ redirectTo }) +}) // with koa router.post('/interaction/:uid', async (ctx, next) => { - const redirectTo = await provider.interactionResult(ctx.req, ctx.res, result); + const redirectTo = await provider.interactionResult(ctx.req, ctx.res, result) - ctx.body = { redirectTo }; -}); + ctx.body = { redirectTo } +}) ``` - ## Custom Grant Types + oidc-provider comes with the basic grants implemented, but you can register your own grant types, -for example to implement an +for example to implement an [OAuth 2.0 Token Exchange](https://www.rfc-editor.org/rfc/rfc8693.html). You can check the standard grant factories [here](/lib/actions/grants). ```js 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'; + '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); +provider.registerGrantType( + grantType, + tokenExchangeHandler, + parameters, + allowedDuplicateParameters, +) ``` - ## 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. ```js -import helmet from 'koa-helmet'; +import helmet from 'koa-helmet' // Correct, pushes koa-helmet at the end of the middleware stack but BEFORE oidc-provider. -provider.use(helmet()); +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()); +provider.app.use(helmet()) ``` - ## Pre- and post-middlewares + You can push custom middleware to be executed before and after oidc-provider. ```js @@ -256,9 +266,9 @@ 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); + console.log('pre middleware', ctx.method, ctx.path) - await next(); + 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 @@ -292,84 +302,91 @@ provider.use(async (ctx, next) => { * `token` * `userinfo` */ - console.log('post middleware', ctx.method, ctx.oidc.route); -}); + console.log('post middleware', 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 `/oidc`. -Note: if you mount oidc-provider to a path it's likely you will have to also update the +Note: if you mount oidc-provider to a path it's likely you will have to also update the [`interactions.url`](#interactionsurl) configuration to reflect the new path. ### to a `connect` application + ```js // assumes connect ^3.0.0 -connectApp.use('/oidc', oidc.callback()); +connectApp.use('/oidc', oidc.callback()) ``` ### to a `fastify` application + ```js // assumes fastify ^4.0.0 -const fastify = new Fastify(); -await fastify.register(require('@fastify/middie')); +const fastify = new Fastify() +await fastify.register(require('@fastify/middie')) // or // await app.register(require('@fastify/express')); -fastify.use('/oidc', oidc.callback()); +fastify.use('/oidc', oidc.callback()) ``` ### to a `hapi` application + ```js // assumes @hapi/hapi ^21.0.0 -const callback = oidc.callback(); +const callback = oidc.callback() hapiApp.route({ path: `/oidc/{any*}`, method: '*', config: { payload: { output: 'stream', parse: false } }, async handler({ raw: { req, res } }, h) { - req.originalUrl = req.url; - req.url = req.url.replace('/oidc', ''); + req.originalUrl = req.url + req.url = req.url.replace('/oidc', '') - callback(req, res); - await once(res, 'finish'); + callback(req, res) + await once(res, 'finish') - req.url = req.url.replace('/', '/oidc'); - delete req.originalUrl; + req.url = req.url.replace('/', '/oidc') + delete req.originalUrl - return res.finished ? h.abandon : h.continue; - } -}); + return res.finished ? h.abandon : h.continue + }, +}) ``` ### to a `nest` application + ```ts // assumes NestJS ^7.0.0 -import { Controller, All, Req, Res } from '@nestjs/common'; -import { Request, Response } from 'express'; -const callback = oidc.callback(); +import { Controller, All, Req, Res } from '@nestjs/common' +import { Request, Response } from 'express' +const callback = oidc.callback() @Controller('oidc') export class OidcController { @All('/*') public mountedOidc(@Req() req: Request, @Res() res: Response): void { - req.url = req.originalUrl.replace('/oidc', ''); - return callback(req, res); + req.url = req.originalUrl.replace('/oidc', '') + return callback(req, res) } } ``` ### to an `express` application + ```js // assumes express ^4.0.0 -expressApp.use('/oidc', oidc.callback()); +expressApp.use('/oidc', oidc.callback()) ``` ### to a `koa` application + ```js // assumes koa ^2.0.0 // assumes koa-mount ^4.0.0 -import mount from 'koa-mount'; -koaApp.use(mount('/oidc', oidc.app)); +import mount from 'koa-mount' +koaApp.use(mount('/oidc', oidc.app)) ``` Note: when the issuer identifier does not include the path prefix you should take care of rewriting @@ -390,15 +407,15 @@ 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 an `express` application | `provider.proxy = true` | -| oidc-provider mounted to a `connect` application | `provider.proxy = true` | -| oidc-provider mounted to a `koa` application | `yourKoaApp.proxy = true` | -| oidc-provider mounted to a `fastify` application | `provider.proxy = true` | -| oidc-provider mounted to a `hapi` application | `provider.proxy = true` | -| oidc-provider mounted to a `nest` application | `provider.proxy = true` | +| setup | example | +| ------------------------------------------------- | ------------------------- | +| standalone oidc-provider | `provider.proxy = true` | +| oidc-provider mounted to an `express` application | `provider.proxy = true` | +| oidc-provider mounted to a `connect` application | `provider.proxy = true` | +| oidc-provider mounted to a `koa` application | `yourKoaApp.proxy = true` | +| oidc-provider mounted to a `fastify` application | `provider.proxy = true` | +| oidc-provider mounted to a `hapi` application | `provider.proxy = true` | +| oidc-provider mounted to a `nest` application | `provider.proxy = true` | It is also necessary that the web server doing the offloading also passes those headers to the downstream application. Here is a common configuration @@ -418,7 +435,6 @@ location / { } ``` - ## Configuration options **Table of Contents** @@ -3810,12 +3826,12 @@ https://www.rfc-editor.org/rfc/rfc6749.html#appendix-B Example: ```js -const client_id = 'an:identifier'; -const client_secret = 'some secure & non-standard secret'; +const client_id = 'an:identifier' +const client_secret = 'some secure & non-standard secret' // After formencoding these two tokens -const encoded_id = 'an%3Aidentifier'; -const encoded_secret = 'some+secure+%26+non%2Dstandard+secret'; +const encoded_id = 'an%3Aidentifier' +const encoded_secret = 'some+secure+%26+non%2Dstandard+secret' // Basic auth header format Authorization: Basic base64(encoded_id + ':' + encoded_secret) // Authorization: Basic YW4lM0FpZGVudGlmaWVyOnNvbWUrc2VjdXJlKyUyNitub24lMkRzdGFuZGFyZCtzZWNyZXQ= @@ -3835,34 +3851,37 @@ listeners for errors deliver them to client developers out-of-band, e.g. by logs in an admin interface. ```js -function handleClientAuthErrors({ headers: { authorization }, oidc: { body, client } }, err) { +function handleClientAuthErrors( + { headers: { authorization }, oidc: { body, client } }, + err, +) { if (err.statusCode === 401 && err.message === 'invalid_client') { // console.log(err); // save error details out-of-bands for the client developers, `authorization`, `body`, `client` // are just some details available, you can dig in ctx object for more. } } -provider.on('grant.error', handleClientAuthErrors); -provider.on('introspection.error', handleClientAuthErrors); -provider.on('revocation.error', handleClientAuthErrors); +provider.on('grant.error', handleClientAuthErrors) +provider.on('introspection.error', handleClientAuthErrors) +provider.on('revocation.error', handleClientAuthErrors) ``` ### Refresh Tokens - > I'm not getting refresh_token from token_endpoint grant_type=authorization_code responses, why? +> I'm not getting refresh_token from token_endpoint grant_type=authorization_code responses, why? Do you support offline_access scope and consent prompt? Did the client request them in the authentication request? - > Yeah, still no refresh_token +> Yeah, still no refresh_token Does the client have grant_type=refresh_token configured? - > Aaaah, that was it. (or one of the above if you follow [Core 1.0#OfflineAccess](http://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess)) +> Aaaah, that was it. (or one of the above if you follow [Core 1.0#OfflineAccess](http://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess)) -*** +--- - > The Authorization Server MAY grant Refresh Tokens in other contexts that are beyond the scope of this specification. How about that? +> The Authorization Server MAY grant Refresh Tokens in other contexts that are beyond the scope of this specification. How about that? Yeah, yeah, see [configuration](#issuerefreshtoken) @@ -3915,6 +3934,5 @@ none. You're getting this error because they are required properties, but they c } ``` - [support-sponsor]: https://github.com/sponsors/panva [sponsor-auth0]: https://auth0.com/signup?utm_source=external_sites&utm_medium=panva&utm_campaign=devn_signup