-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathAuthenticationRequest.js
More file actions
192 lines (157 loc) · 5.92 KB
/
Copy pathAuthenticationRequest.js
File metadata and controls
192 lines (157 loc) · 5.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
/**
* Dependencies
*/
const assert = require('assert')
const base64url = require('base64url')
const crypto = require('@trust/webcrypto')
const { JWT } = require('@solid/jose')
const { URL } = require('whatwg-url')
/**
* Authentication Request
*/
class AuthenticationRequest {
/**
* create
*
* @description
* Create a new authentication request with generated state and nonce,
* validate presence of required parameters, serialize the request data and
* persist it to the session, and return a promise for an authentication
* request URI.
*
* @param {RelyingParty} rp – instance of RelyingParty
* @param {Object} options - optional request parameters
* @param {Object} session – reference to localStorage or other session object
*
* @returns {Promise}
*/
static create (rp, options, session) {
const {provider, defaults, registration} = rp
let issuer, endpoint, client, params
return Promise.resolve()
.then(() => {
// validate presence of OP configuration, RP client registration,
// and default parameters
assert(provider.configuration,
'RelyingParty provider OpenID Configuration is missing')
assert(defaults.authenticate,
'RelyingParty default authentication parameters are missing')
assert(registration,
'RelyingParty client registration is missing')
// define basic elements of the request
issuer = provider.configuration.issuer
endpoint = provider.configuration.authorization_endpoint
client = { client_id: registration.client_id}
params = Object.assign(defaults.authenticate, client, options)
// validate presence of required configuration and parameters
assert(issuer,
'Missing issuer in provider OpenID Configuration')
assert(endpoint,
'Missing authorization_endpoint in provider OpenID Configuration')
assert(params.scope,
'Missing scope parameter in authentication request')
assert(params.response_type,
'Missing response_type parameter in authentication request')
assert(params.client_id,
'Missing client_id parameter in authentication request')
assert(params.redirect_uri,
'Missing redirect_uri parameter in authentication request')
// generate state and nonce random octets
params.state = Array.from(crypto.getRandomValues(new Uint8Array(16)))
params.nonce = Array.from(crypto.getRandomValues(new Uint8Array(16)))
// hash the state and nonce parameter values
return Promise.all([
crypto.subtle.digest({ name: 'SHA-256' }, new Uint8Array(params.state)),
crypto.subtle.digest({ name: 'SHA-256' }, new Uint8Array(params.nonce))
])
})
// serialize the request with original values, store in session by
// encoded state param, and replace state/nonce octets with encoded
// digests
.then(digests => {
let state = base64url(Buffer.from(digests[0]))
let nonce = base64url(Buffer.from(digests[1]))
let key = `${issuer}/requestHistory/${state}`
// store the request params for response validation
// with serialized octet values for state and nonce
session[key] = JSON.stringify(params)
// replace state and nonce octets with base64url encoded digests
params.state = state
params.nonce = nonce
})
.then(() => AuthenticationRequest.generateSessionKeys())
.then(sessionKeys => {
AuthenticationRequest.storeSessionKeys(sessionKeys, params, session)
})
// optionally encode a JWT with the request parameters
// and replace params with `{ request: <jwt> }
.then(() => {
if (provider.configuration.request_parameter_supported) {
return AuthenticationRequest.encodeRequestParams(params)
.then(encodedParams => { params = encodedParams })
}
})
// render the request URI and terminate the algorithm
.then(() => {
let url = new URL(endpoint)
// combine with any exists query parameters.
Object.entries(params).map(param => url.searchParams.append(param[0], param[1]))
return url.href
})
}
static generateSessionKeys () {
return crypto.subtle.generateKey(
{
name: "RSASSA-PKCS1-v1_5",
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: { name: "SHA-256" },
},
true,
["sign", "verify"]
)
.then((keyPair) => {
// returns a keypair object
return Promise.all([
crypto.subtle.exportKey('jwk', keyPair.publicKey),
crypto.subtle.exportKey('jwk', keyPair.privateKey)
])
})
.then(jwkPair => {
let [ publicJwk, privateJwk ] = jwkPair
return { public: publicJwk, private: privateJwk }
})
}
static storeSessionKeys (sessionKeys, params, session) {
// store the private one in session, public one goes into params
session['oidc.session.privateKey'] = JSON.stringify(sessionKeys.private)
params.key = sessionKeys.public
}
static encodeRequestParams (params) {
const excludeParams = ['scope', 'client_id', 'response_type', 'state']
const keysToEncode = Object.keys(params).filter(key => !excludeParams.includes(key))
let payload = {}
keysToEncode.forEach(key => {
payload[key] = params[key]
})
let requestParamJwt = new JWT({
header: { alg: 'none' },
payload
}, { filter: false })
return requestParamJwt.encode()
.then(requestParamCompact => {
let newParams = {
scope: params['scope'],
client_id: params['client_id'],
response_type: params['response_type'],
request: requestParamCompact,
state: params['state']
}
return newParams
})
}
}
/**
* Export
*/
module.exports = AuthenticationRequest