@muze-nl/metro-oidc 0.5.6 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +3 -50
- package/dist/browser.js +1942 -498
- package/dist/browser.js.map +4 -4
- package/dist/browser.min.js +3 -1
- package/dist/browser.min.js.map +4 -4
- package/docs/README.md +30 -0
- package/docs/reference.md +97 -0
- package/package.json +47 -15
- package/src/browser.mjs +9 -15
- package/src/index.mjs +20 -0
- package/src/oidc.discovery.mjs +4 -3
- package/src/oidc.jwt.mjs +65 -0
- package/src/oidc.mockserver.mjs +195 -0
- package/src/oidc.register.mjs +6 -5
- package/src/oidc.util.mjs +1 -1
- package/src/oidcmw.mjs +60 -24
- package/src/testing.mjs +4 -0
package/src/oidcmw.mjs
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import * as metro from '@muze-nl/metro
|
|
2
|
-
import oauth2mw, * as oauth2 from '@muze-nl/metro-oauth2/
|
|
3
|
-
import dpopmw from '@muze-nl/metro-oauth2/
|
|
1
|
+
import * as metro from '@muze-nl/metro-core'
|
|
2
|
+
import oauth2mw, * as oauth2 from '@muze-nl/metro-oauth2/oauth2'
|
|
3
|
+
import dpopmw from '@muze-nl/metro-oauth2/dpop'
|
|
4
4
|
import { assert, Required, Optional, validURL, instanceOf } from '@muze-nl/assert'
|
|
5
5
|
import discover from './oidc.discovery.mjs'
|
|
6
6
|
import register from './oidc.register.mjs'
|
|
7
7
|
import oidcStore from './oidc.store.mjs'
|
|
8
|
+
import jsonmw from '@muze-nl/metro-middleware/json'
|
|
9
|
+
import throwermw from '@muze-nl/metro-middleware/thrower'
|
|
10
|
+
import { validateIdToken } from './oidc.jwt.mjs'
|
|
8
11
|
|
|
9
12
|
export default function oidcmw(options={}) {
|
|
10
13
|
|
|
@@ -46,7 +49,8 @@ export default function oidcmw(options={}) {
|
|
|
46
49
|
try {
|
|
47
50
|
res = await next(req)
|
|
48
51
|
} catch(err) {
|
|
49
|
-
|
|
52
|
+
res = err?.cause
|
|
53
|
+
if (!res || (res.status!=401 && res.status!=403)) {
|
|
50
54
|
throw err
|
|
51
55
|
}
|
|
52
56
|
}
|
|
@@ -56,7 +60,8 @@ export default function oidcmw(options={}) {
|
|
|
56
60
|
}
|
|
57
61
|
if (!options.openid_configuration) {
|
|
58
62
|
options.openid_configuration = await discover({
|
|
59
|
-
issuer: options.issuer
|
|
63
|
+
issuer: options.issuer,
|
|
64
|
+
client: options.client.with(options.issuer)
|
|
60
65
|
})
|
|
61
66
|
options.store.set('openid_configuration', options.openid_configuration)
|
|
62
67
|
}
|
|
@@ -67,6 +72,7 @@ export default function oidcmw(options={}) {
|
|
|
67
72
|
}
|
|
68
73
|
options.client_info = await register({
|
|
69
74
|
registration_endpoint: options.openid_configuration.registration_endpoint,
|
|
75
|
+
client: options.client,
|
|
70
76
|
client_info: options.client_info
|
|
71
77
|
})
|
|
72
78
|
options.store.set('client_info', options.client_info)
|
|
@@ -75,6 +81,8 @@ export default function oidcmw(options={}) {
|
|
|
75
81
|
// now initialize an oauth2 client stack, using options.client as default
|
|
76
82
|
// with forceAuthentication: true
|
|
77
83
|
const scope = options.scope || 'openid'
|
|
84
|
+
const nonce = options.nonce || oauth2.generateCodeVerifier(32)
|
|
85
|
+
options.store.set('pending_nonce', nonce)
|
|
78
86
|
|
|
79
87
|
const oauth2Options = Object.assign(
|
|
80
88
|
{
|
|
@@ -91,7 +99,8 @@ export default function oidcmw(options={}) {
|
|
|
91
99
|
authorization_endpoint: options.openid_configuration.authorization_endpoint,
|
|
92
100
|
token_endpoint: options.openid_configuration.token_endpoint,
|
|
93
101
|
scope, //FIXME: should only use scopes supported by server
|
|
94
|
-
redirect_uri: options.client_info.redirect_uris[0]
|
|
102
|
+
redirect_uri: options.client_info.redirect_uris[0],
|
|
103
|
+
nonce
|
|
95
104
|
}
|
|
96
105
|
}
|
|
97
106
|
//...
|
|
@@ -99,28 +108,45 @@ export default function oidcmw(options={}) {
|
|
|
99
108
|
|
|
100
109
|
const storeIdToken = async (req, next) => {
|
|
101
110
|
const res = await next(req)
|
|
111
|
+
const tokenEndpoint = metro.url(options.openid_configuration.token_endpoint, { hash: '' }).href
|
|
112
|
+
const requestUrl = metro.url(req.url, { hash: '' }).href
|
|
113
|
+
if (requestUrl !== tokenEndpoint) {
|
|
114
|
+
return res
|
|
115
|
+
}
|
|
102
116
|
const contentType = res.headers.get('content-type')
|
|
103
|
-
if (contentType?.startsWith('application/json')) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
id_token = data.id_token
|
|
112
|
-
}
|
|
113
|
-
} catch(e) {
|
|
114
|
-
// ignore errors
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
if (id_token) {
|
|
118
|
-
options.store.set('id_token', id_token)
|
|
119
|
-
}
|
|
117
|
+
if (!contentType?.startsWith('application/json')) {
|
|
118
|
+
return res
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let data = res.data && typeof res.data === 'object' ? res.data : null
|
|
122
|
+
if (!data) {
|
|
123
|
+
const res2 = res.clone() // otherwise res.body can't be read again
|
|
124
|
+
data = await res2.json()
|
|
120
125
|
}
|
|
126
|
+
|
|
127
|
+
const id_token = data?.id_token
|
|
128
|
+
const jwks = await getJwks()
|
|
129
|
+
const validation = await validateIdToken(id_token, {
|
|
130
|
+
issuer: options.openid_configuration.issuer,
|
|
131
|
+
client_id: options.client_info.client_id,
|
|
132
|
+
jwks,
|
|
133
|
+
openid_configuration: options.openid_configuration,
|
|
134
|
+
nonce: options.store.get('pending_nonce')
|
|
135
|
+
})
|
|
136
|
+
options.store.set('id_token', id_token)
|
|
137
|
+
options.store.set('id_token_claims', validation.claims)
|
|
121
138
|
return res
|
|
122
139
|
}
|
|
123
140
|
|
|
141
|
+
const getJwks = async () => {
|
|
142
|
+
if (!options.jwks) {
|
|
143
|
+
const jwksClient = options.client.with(throwermw()).with(jsonmw())
|
|
144
|
+
const response = await jwksClient.get(options.openid_configuration.jwks_uri)
|
|
145
|
+
options.jwks = response.data
|
|
146
|
+
}
|
|
147
|
+
return options.jwks
|
|
148
|
+
}
|
|
149
|
+
|
|
124
150
|
let oauth2client = options.client.with(options.issuer).with(storeIdToken)
|
|
125
151
|
|
|
126
152
|
if (options.use_dpop) {
|
|
@@ -131,8 +157,8 @@ export default function oidcmw(options={}) {
|
|
|
131
157
|
dpop_signing_alg_values_supported: options.openid_configuration.dpop_signing_alg_values_supported
|
|
132
158
|
}
|
|
133
159
|
oauth2client = oauth2client.with(dpopmw(dpopOptions)) // add DPoP headers in requests with Authorization headers
|
|
134
|
-
oauth2Options.client = oauth2client // make sure oath2 token request use dpop
|
|
135
160
|
}
|
|
161
|
+
oauth2Options.client = oauth2client // make sure token requests use the OIDC token-observing stack
|
|
136
162
|
|
|
137
163
|
oauth2client = oauth2client.with(oauth2mw(oauth2Options))
|
|
138
164
|
|
|
@@ -156,3 +182,13 @@ export function idToken(options) {
|
|
|
156
182
|
}
|
|
157
183
|
return options.store.get('id_token')
|
|
158
184
|
}
|
|
185
|
+
|
|
186
|
+
export function idTokenClaims(options) {
|
|
187
|
+
if (!options.store) {
|
|
188
|
+
if (!options.issuer) {
|
|
189
|
+
throw metro.metroError('Must supply options.issuer or options.store to get the id_token claims')
|
|
190
|
+
}
|
|
191
|
+
options.store = oidcStore(options.issuer)
|
|
192
|
+
}
|
|
193
|
+
return options.store.get('id_token_claims')
|
|
194
|
+
}
|
package/src/testing.mjs
ADDED