Skip to content

Commit

Permalink
Feature: OpenID Connect Discovery support (#242)
Browse files Browse the repository at this point in the history
* feat: oidc metadata discovery implementation

* docs: adds discovery example in readme

* feat(types): add type defs and tests

* docs: better docs

* test: swap test names to match the intent

* ci: just an empty commit to trigger GA

* fix: address pkce method selection when op doesn't announce it in discovery

* test: fix tests for inconsistent JSON parse err between node versions

* feat: better handling for missing endpoints - either can be ommited
  • Loading branch information
big-kahuna-burger authored Nov 23, 2023
1 parent 8ac6ba6 commit 9c0e77d
Show file tree
Hide file tree
Showing 6 changed files with 1,023 additions and 132 deletions.
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,43 @@ fastify.register(oauthPlugin, {
})
```

## Use automated discovery endpoint

When your provider supports OpenID connect discovery and you want to configure authorization, token and revocation endpoints automatically,
then you can use discovery option.
`discovery` is a simple object that requires `issuer` property.

Issuer is expected to be string URL or metadata url.
Variants with or without trailing slash are supported.

You can see more in [example here](./examples/discovery.js).

```js
fastify.register(oauthPlugin, {
name: 'customOAuth2',
scope: ['profile', 'email'],
credentials: {
client: {
id: '<CLIENT_ID>',
secret: '<CLIENT_SECRET>',
},
// Note how "auth" is not needed anymore when discovery is used.
},
startRedirectPath: '/login',
callbackUri: 'http://localhost:3000/callback',
discovery: { issuer: 'https://identity.mycustomdomain.com' }
// pkce: 'S256', you can still do this explicitly, but since discovery is used,
// it's BEST to let plugin do it itself
// based on what Authorization Server Metadata response
});
```

Important notes for discovery:

- You should not set up `credentials.auth` anymore when discovery mechanics is used.
- When your provider supports it, plugin will also select appropriate PKCE method in authorization code grant
- In case you still want to select method yourself, and know exactly what you are doing; you can still do it explicitly.

### Schema configuration

You can specify your own schema for the `startRedirectPath` end-point. It allows you to create a well-documented document when using `@fastify/swagger` together.
Expand Down
89 changes: 89 additions & 0 deletions examples/discovery.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
'use strict'

const fastify = require('fastify')({ logger: { level: 'trace' } })
const sget = require('simple-get')

const cookieOpts = {
// domain: 'localhost',
path: '/',
secure: true,
sameSite: 'lax',
httpOnly: true
}

// const oauthPlugin = require('fastify-oauth2')
fastify.register(require('@fastify/cookie'), {
secret: ['my-secret'],
parseOptions: cookieOpts
})

const oauthPlugin = require('..')
fastify.register(oauthPlugin, {
name: 'googleOAuth2',
// when provided, this userAgent will also be used at discovery endpoint
// to fully omit for whatever reason, set it to false
userAgent: 'my custom app (v1.0.0)',
scope: ['openid', 'profile', 'email'],
credentials: {
client: {
id: process.env.CLIENT_ID,
secret: process.env.CLIENT_SECRET
}
},
startRedirectPath: '/login/google',
callbackUri: 'http://localhost:3000/interaction/callback/google',
cookie: cookieOpts,
// pkce: 'S256' let discovery handle it itself
discovery: {
/*
When OIDC provider is mounted at root:
with trailing slash (99% of the cases)
- 'https://accounts.google.com/'
*/
issuer: 'https://accounts.google.com'
/*
also these variants work:
When OIDC provider is mounted at root:
with trailing slash
- 'https://accounts.google.com/'
When given explicit metadata endpoint:
- issuer: 'https://accounts.google.com/.well-known/openid-configuration'
When OIDC provider is nested at some route:
- with trailing slash
'https://id.mycustomdomain.com/nested/'
- without trailing slash
'https://id.mycustomdomain.com/nested'
*/
}
})

fastify.get('/interaction/callback/google', function (request, reply) {
// Note that in this example a "reply" is also passed, it's so that code verifier cookie can be cleaned before
// token is requested from token endpoint
this.googleOAuth2.getAccessTokenFromAuthorizationCodeFlow(request, reply, (err, result) => {
if (err) {
reply.send(err)
return
}

sget.concat({
url: 'https://www.googleapis.com/oauth2/v2/userinfo',
method: 'GET',
headers: {
Authorization: 'Bearer ' + result.token.access_token
},
json: true
}, function (err, res, data) {
if (err) {
reply.send(err)
return
}
reply.send(data)
})
})
})

fastify.listen({ port: 3000 })
fastify.log.info('go to http://localhost:3000/login/google')
Loading

0 comments on commit 9c0e77d

Please sign in to comment.