Skip to content

Commit 9c0e77d

Browse files
Feature: OpenID Connect Discovery support (#242)
* 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
1 parent 8ac6ba6 commit 9c0e77d

File tree

6 files changed

+1023
-132
lines changed

6 files changed

+1023
-132
lines changed

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,43 @@ fastify.register(oauthPlugin, {
120120
})
121121
```
122122

123+
## Use automated discovery endpoint
124+
125+
When your provider supports OpenID connect discovery and you want to configure authorization, token and revocation endpoints automatically,
126+
then you can use discovery option.
127+
`discovery` is a simple object that requires `issuer` property.
128+
129+
Issuer is expected to be string URL or metadata url.
130+
Variants with or without trailing slash are supported.
131+
132+
You can see more in [example here](./examples/discovery.js).
133+
134+
```js
135+
fastify.register(oauthPlugin, {
136+
name: 'customOAuth2',
137+
scope: ['profile', 'email'],
138+
credentials: {
139+
client: {
140+
id: '<CLIENT_ID>',
141+
secret: '<CLIENT_SECRET>',
142+
},
143+
// Note how "auth" is not needed anymore when discovery is used.
144+
},
145+
startRedirectPath: '/login',
146+
callbackUri: 'http://localhost:3000/callback',
147+
discovery: { issuer: 'https://identity.mycustomdomain.com' }
148+
// pkce: 'S256', you can still do this explicitly, but since discovery is used,
149+
// it's BEST to let plugin do it itself
150+
// based on what Authorization Server Metadata response
151+
});
152+
```
153+
154+
Important notes for discovery:
155+
156+
- You should not set up `credentials.auth` anymore when discovery mechanics is used.
157+
- When your provider supports it, plugin will also select appropriate PKCE method in authorization code grant
158+
- In case you still want to select method yourself, and know exactly what you are doing; you can still do it explicitly.
159+
123160
### Schema configuration
124161

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

examples/discovery.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
'use strict'
2+
3+
const fastify = require('fastify')({ logger: { level: 'trace' } })
4+
const sget = require('simple-get')
5+
6+
const cookieOpts = {
7+
// domain: 'localhost',
8+
path: '/',
9+
secure: true,
10+
sameSite: 'lax',
11+
httpOnly: true
12+
}
13+
14+
// const oauthPlugin = require('fastify-oauth2')
15+
fastify.register(require('@fastify/cookie'), {
16+
secret: ['my-secret'],
17+
parseOptions: cookieOpts
18+
})
19+
20+
const oauthPlugin = require('..')
21+
fastify.register(oauthPlugin, {
22+
name: 'googleOAuth2',
23+
// when provided, this userAgent will also be used at discovery endpoint
24+
// to fully omit for whatever reason, set it to false
25+
userAgent: 'my custom app (v1.0.0)',
26+
scope: ['openid', 'profile', 'email'],
27+
credentials: {
28+
client: {
29+
id: process.env.CLIENT_ID,
30+
secret: process.env.CLIENT_SECRET
31+
}
32+
},
33+
startRedirectPath: '/login/google',
34+
callbackUri: 'http://localhost:3000/interaction/callback/google',
35+
cookie: cookieOpts,
36+
// pkce: 'S256' let discovery handle it itself
37+
discovery: {
38+
/*
39+
When OIDC provider is mounted at root:
40+
with trailing slash (99% of the cases)
41+
- 'https://accounts.google.com/'
42+
*/
43+
issuer: 'https://accounts.google.com'
44+
/*
45+
also these variants work:
46+
When OIDC provider is mounted at root:
47+
with trailing slash
48+
- 'https://accounts.google.com/'
49+
50+
When given explicit metadata endpoint:
51+
- issuer: 'https://accounts.google.com/.well-known/openid-configuration'
52+
53+
When OIDC provider is nested at some route:
54+
- with trailing slash
55+
'https://id.mycustomdomain.com/nested/'
56+
- without trailing slash
57+
'https://id.mycustomdomain.com/nested'
58+
*/
59+
}
60+
})
61+
62+
fastify.get('/interaction/callback/google', function (request, reply) {
63+
// Note that in this example a "reply" is also passed, it's so that code verifier cookie can be cleaned before
64+
// token is requested from token endpoint
65+
this.googleOAuth2.getAccessTokenFromAuthorizationCodeFlow(request, reply, (err, result) => {
66+
if (err) {
67+
reply.send(err)
68+
return
69+
}
70+
71+
sget.concat({
72+
url: 'https://www.googleapis.com/oauth2/v2/userinfo',
73+
method: 'GET',
74+
headers: {
75+
Authorization: 'Bearer ' + result.token.access_token
76+
},
77+
json: true
78+
}, function (err, res, data) {
79+
if (err) {
80+
reply.send(err)
81+
return
82+
}
83+
reply.send(data)
84+
})
85+
})
86+
})
87+
88+
fastify.listen({ port: 3000 })
89+
fastify.log.info('go to http://localhost:3000/login/google')

0 commit comments

Comments
 (0)