Skip to content

Commit d57e832

Browse files
feat: OpenID Connect scheme (#868)
* WIP - Add support for OpenIDConnect * WIP - remove client process import from discovery document * WIP - make oidcmockserver port dynamic * WIP - add first e2e tests for OIDC scheme Initial state, login and refresh * WIP - OIDC - Add logout e2e test * WIP - OIDC - Remove unused * WIP - OIDC - get userinfo from ID token * WIP - OIDC - Refine tests * WIP - OIDC - Remove unnecessary OIDC mock server config * WIP - OIDC - Fix callback on demo * WIP - OIDC - Fix type extensions * WIP - OIDC - Refine Discovery Document load * WIP - OIDC - Upgrade to TypeScript v4 * WIP - Introduce jest-puppeteer * WIP - Refine OIDC tests, add multiple modes, add custom fixture test * WIP - Refactor e2e test to use jest-puppeteer * WIP - Rename discovery document to configuration document * WIP - Validate strategy configuration against Authorization Server configuration * WIP - Rename scheme name from 'oidc' to 'openIDConnect' * WIP - OIDC - Fix tests after migration to ts-jest * WIP - OIDC - Set oidc-provider version to latest * WIP - OIDC - Ignore ConfigurationDocumentWarning console.warn * WIP - OIDC - Update CI tests to not build and to run in band * WIP - OIDC - Remove optional chaining for Node 10 compatibility * WIP - OIDC - Update jest-puppeteer in yarn.lock * WIP - OIDC - Use ES modules in tests * WIP - Lint fix * WIP - IdToken - Add JwtPayload type to jwtDecode * WIP - Upgrade oidc-provider * WIP - OIDC - Wait for navigation to consent-page * WIP - OIDC - Wait for element * WIP - OIDC - Fix idToken in handleCallback response, fix types, enhance configuration document validation * WIP - OIDC - Remove jest-puppeteer in favor of nuxt test utils * WIP - OIDC - fix getting idToken from refresh token response * WIP - OIDC - Sync to upstream * WIP - OIDC - Reactivate node oidc provider logging * WIP - OIDC - Move ConfigurationDocumentRequestError to own file * WIP - OIDC - Remove unused comment * WIP - OIDC - Update @nuxt/test-utils * WIP - OIDC - Reenable parallel test runs * WIP - OIDC - Revert 83996b5 * WIP - OIDC - Fix tests * WIP - OIDC - Use ssr config prop in favor of deprecated mode * WIP - OIDC - Fix tests by upgrading to @nuxt/test-utils v0.2.0 * WIP - OIDC - Add default scheme options * WIP - OIDC - Add snippet of information to configuration document * WIP - OIDC - Shortened default ID token lifetime to 30 minutes * WIP - OIDC - Documentation * WIP - OIDC - Run tests parallel * WIP - OIDC - Fix code examples in docs Co-authored-by: Miguel A. Calles <[email protected]> Co-authored-by: Miguel A. Calles <[email protected]>
1 parent 0e66dcd commit d57e832

23 files changed

+1702
-41
lines changed

demo/api/oidcmockserver.js

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
const { Provider } = require('oidc-provider')
2+
const defu = require('defu')
3+
4+
// Suppress oidc-provider logging on test run
5+
if (process.env.NODE_ENV === 'test') {
6+
// eslint-disable-next-line no-console
7+
console.warn = () => {
8+
/* Do nothing */
9+
}
10+
// eslint-disable-next-line no-console
11+
console.info = () => {
12+
/* Do nothing */
13+
}
14+
}
15+
16+
const DEFAULTS = {
17+
port: 3000,
18+
path: '/oidc',
19+
redirect: {
20+
callback: '/login'
21+
}
22+
}
23+
24+
const provider = (config) => {
25+
const { port, redirect, path } = defu(config, DEFAULTS)
26+
const baseUrl = `http://localhost:${port}${path}`
27+
const appBaseUrl = `http://localhost:${port}`
28+
const redirectUri = `${appBaseUrl}${redirect.callback}`
29+
30+
return new Provider(baseUrl, {
31+
routes: {
32+
authorization: '/connect/authorize',
33+
token: '/connect/token',
34+
userinfo: '/connect/userinfo',
35+
end_session: '/connect/endsession'
36+
},
37+
features: {
38+
devInteractions: { enabled: true },
39+
revocation: { enabled: true }
40+
},
41+
scopes: ['openid', 'profile', 'offline_access'],
42+
clients: [
43+
{
44+
client_id: 'oidc_authorization_code_client',
45+
client_secret: 'this_is_a_secret',
46+
token_endpoint_auth_method: 'none',
47+
grant_types: ['authorization_code', 'refresh_token'],
48+
response_types: ['code'],
49+
redirect_uris: [redirectUri],
50+
post_logout_redirect_uris: [appBaseUrl]
51+
}
52+
],
53+
54+
// Force refresh token issueing
55+
issueRefreshToken: () => Promise.resolve(true)
56+
})
57+
}
58+
59+
module.exports = (config = {}) => provider(config).callback

demo/nuxt.config.ts

+27-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,24 @@
11
import { NuxtConfig } from '@nuxt/types'
2+
import oidcMockServer from './api/oidcmockserver'
23

34
export default <NuxtConfig>{
45
build: {
56
extractCSS: true
67
},
7-
serverMiddleware: ['~/api/auth', '~/api/oauth2mockserver'],
8+
serverMiddleware: [
9+
'~/api/auth',
10+
'~/api/oauth2mockserver',
11+
{
12+
path: '/oidc',
13+
handler: oidcMockServer({
14+
port: 3000,
15+
path: '/oidc',
16+
redirect: {
17+
callback: '/callback'
18+
}
19+
})
20+
}
21+
],
822
buildModules: ['@nuxt/typescript-build'],
923
modules: ['bootstrap-vue/nuxt', '@nuxtjs/axios', '../src/module'],
1024
components: true,
@@ -131,6 +145,18 @@ export default <NuxtConfig>{
131145
responseType: 'code',
132146
grantType: 'authorization_code',
133147
clientId: 'test-client'
148+
},
149+
oidcmock: {
150+
scheme: 'openIDConnect',
151+
responseType: 'code',
152+
scope: ['openid', 'profile', 'offline_access'],
153+
grantType: 'authorization_code',
154+
clientId: 'oidc_authorization_code_client',
155+
logoutRedirectUri: 'http://localhost:3000',
156+
endpoints: {
157+
configuration:
158+
'http://localhost:3000/oidc/.well-known/openid-configuration'
159+
}
134160
}
135161
}
136162
}

demo/pages/login.vue

+10
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@
6262
Login with oauth2
6363
</b-btn>
6464
</div>
65+
<div class="mb-2">
66+
<b-btn
67+
block
68+
:style="{ background: '#e0640b' }"
69+
class="login-button"
70+
@click="$auth.loginWith('oidcmock')"
71+
>
72+
Login with OpenIDConnect
73+
</b-btn>
74+
</div>
6575
<div class="mb-2">
6676
<b-btn
6777
block
+189
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
---
2+
title: OpenIDConnect
3+
description: OpenID Connect 1.0 is a simple identity layer on top of the OAuth 2.0 protocol. It enables Clients to verify the identity of the End-User based on the authentication performed by an Authorization Server, as well as to obtain basic profile information about the End-User in an interoperable and REST-like manner.
4+
position: 23
5+
category: Schemes
6+
---
7+
8+
[Source Code](https://github.com/nuxt-community/auth-module/blob/dev/src/schemes/openIDConnect.ts)
9+
10+
As the OpenID Connect is a layer on top of the OAuth 2.0 protocol, this scheme extends the OAuth 2.0 scheme.
11+
12+
Please see the [OAuth2 scheme](./oauth2) for more information.
13+
14+
## Usage
15+
16+
```js
17+
this.$auth.loginWith('openIDConnect')
18+
```
19+
20+
Additional arguments can be passed through to the OpenID Connect provider using the `params` key of the second argument:
21+
22+
```js
23+
this.$auth.loginWith('openIDConnect', { params: { another_post_key: 'value' } })
24+
```
25+
26+
## Options
27+
Minimal configuration:
28+
```js
29+
auth: {
30+
strategies: {
31+
oidc: {
32+
scheme: 'openIDConnect',
33+
clientId: 'CLIENT_ID',
34+
endpoints: {
35+
configuration: 'https://accounts.google.com/.well-known/openid-configuration',
36+
},
37+
}
38+
}
39+
}
40+
```
41+
42+
Default configuration:
43+
```js
44+
auth: {
45+
strategies: {
46+
oidc: {
47+
scheme: 'openIDConnect',
48+
endpoints: {
49+
configuration: 'https://accounts.google.com/.well-known/openid-configuration',
50+
},
51+
idToken: {
52+
property: 'id_token',
53+
maxAge: 60 * 60 * 24 * 30,
54+
prefix: '_id_token.',
55+
expirationPrefix: '_id_token_expiration.'
56+
},
57+
responseType: 'code',
58+
grantType: 'authorization_code',
59+
scope: ['openid', 'profile', 'offline_access'],
60+
codeChallengeMethod: 'S256',
61+
}
62+
}
63+
}
64+
```
65+
66+
### `endpoints`
67+
68+
Each endpoint is used to make requests using axios. They are basically extending Axios [Request Config](https://github.com/axios/axios#request-config).
69+
70+
#### `configuration`
71+
72+
**REQUIRED** - Endpoint to request the provider's metadata document to automatically set the endpoints. A metadata document that contains most of the OpenID Provider's information, such as the URLs to use and the location of the service's public signing keys. You can find this document by appending the discovery document path (/.well-known/openid-configuration) to the authority URL (https://example.com).
73+
74+
Eg. `https://example.com/.well-known/openid-configuration`
75+
76+
More info: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig
77+
78+
Each endpoint defined in the OAuth2 scheme can also be used in the OpenID Connect scheme configuration. This will override the information provided by the configuration document.
79+
80+
### `clientId`
81+
82+
**REQUIRED** - OpenID Connect client id.
83+
84+
### `scope`
85+
86+
- Default: `['openid', 'profile', 'offline_access']`
87+
88+
OpenID Connect access scopes.
89+
90+
### `token`
91+
92+
Access token
93+
94+
#### `property`
95+
96+
- Default: `access_token`
97+
98+
`property` can be used to specify which field of the response JSON to be used for value. It can be `false` to directly use API response or being more complicated like `auth.access_token`.
99+
100+
#### `type`
101+
102+
- Default: `Bearer`
103+
104+
It will be used in `Authorization` header of axios requests.
105+
106+
#### `maxAge`
107+
108+
- Default: `1800`
109+
110+
Here you set the expiration time of the token, in **seconds**.
111+
This time will be used if for some reason we couldn't decode the token to get the expiration date.
112+
113+
Should be same as login page or relative path to welcome screen. ([example](https://github.com/nuxt-community/auth-module/blob/dev/examples/demo/pages/callback.vue))
114+
115+
By default is set to 30 minutes.
116+
117+
### `idToken`
118+
119+
The OpenIDConnect scheme will save both the access and ID token. This because to end the user-session at the authorization server, the ID token needs to be part of the logout request via the required parameter `id_token_hint`.
120+
121+
#### `property`
122+
123+
- Default: `id_token`
124+
125+
`property` can be used to specify which field of the response JSON to be used for value. It can be `false` to directly use API response or being more complicated like `auth.id_token`.
126+
127+
#### `maxAge`
128+
129+
- Default: `1800`
130+
131+
Here you set the expiration time of the ID token, in **seconds**.
132+
This time will be used if for some reason we couldn't decode the ID token to get the expiration date.
133+
134+
By default is set to 30 minutes.
135+
136+
### `refreshToken`
137+
138+
#### `property`
139+
140+
- Default: `refresh_token`
141+
142+
`property` can be used to specify which field of the response JSON to be used for value. It can be `false` to directly use API response or being more complicated like `auth.refresh_token`.
143+
144+
#### `maxAge`
145+
146+
- Default: `60 * 60 * 24 * 30`
147+
148+
Here you set the expiration time of the refresh token, in **seconds**.
149+
This time will be used if for some reason we couldn't decode the token to get the expiration date.
150+
151+
By default is set to 30 days.
152+
153+
### `responseType`
154+
155+
- Default: `code`
156+
157+
Set to `code` for authorization code flow.
158+
159+
### `grantType`
160+
161+
- Default: `authorization_code`
162+
163+
Set to `authorization_code` for authorization code flow.
164+
165+
### `redirectUri`
166+
167+
Should be same as login page or relative path to welcome screen. ([example](https://github.com/nuxt-community/auth-module/blob/dev/examples/demo/pages/callback.vue))
168+
169+
By default it will be inferred from `redirect.callback` option. (Defaults to `/login`)
170+
171+
### `logoutRedirectUri`
172+
173+
Should be an absolute path to the welcome screen
174+
175+
### `codeChallengeMethod`
176+
177+
By default is 'implicit' which is the current workflow implementation. In order to support PKCE ('pixy') protocol, valid options include 'S256' and 'plain'. ([read more](https://tools.ietf.org/html/rfc7636))
178+
179+
Default: `S256`
180+
181+
### `acrValues`
182+
183+
Provides metadata to supply additional information to the authorization server. ([read more](https://ldapwiki.com/wiki/Acr_values))
184+
185+
### `autoLogout`
186+
187+
- Default: `false`
188+
189+
If the token has expired, it will prevent the token from being refreshed on load the page and force logout the user.

package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"@babel/preset-typescript": "^7.12.13",
5454
"@microsoft/api-documenter": "^7.12.7",
5555
"@microsoft/api-extractor": "^7.13.1",
56-
"@nuxt/test-utils": "^0.1.2",
56+
"@nuxt/test-utils": "^0.2.0",
5757
"@nuxt/types": "latest",
5858
"@nuxt/typescript-build": "latest",
5959
"@nuxt/typescript-runtime": "latest",
@@ -73,13 +73,13 @@
7373
"eslint-plugin-prettier": "latest",
7474
"express": "latest",
7575
"express-jwt": "latest",
76-
"get-port": "latest",
7776
"jest": "^27.0.1",
7877
"jiti": "^1.9.1",
7978
"jsdom": "latest",
8079
"lodash.get": "latest",
8180
"nuxt": "^2.14.12",
82-
"playwright": "^1.8.0",
81+
"oidc-provider": "^6.29.9",
82+
"playwright": "^1.8.1",
8383
"prettier": "latest",
8484
"siroc": "^0.6.3",
8585
"standard-version": "latest",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export class ConfigurationDocumentRequestError extends Error {
2+
constructor() {
3+
super('Error loading OpenIDConnect configuration document')
4+
this.name = 'ConfigurationDocumentRequestError'
5+
}
6+
}

0 commit comments

Comments
 (0)