-
-
Notifications
You must be signed in to change notification settings - Fork 54
/
fapi2-message-signing.ts
163 lines (140 loc) · 5.06 KB
/
fapi2-message-signing.ts
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
import * as oauth from 'oauth4webapi'
// Prerequisites
let getCurrentUrl!: (...args: any) => URL
let issuer!: URL // Authorization server's Issuer Identifier URL
let algorithm!:
| 'oauth2' /* For .well-known/oauth-authorization-server discovery */
| 'oidc' /* For .well-known/openid-configuration discovery */
| undefined /* Defaults to 'oidc' */
let client_id!: string
/**
* Value used in the authorization request as redirect_uri pre-registered at the Authorization
* Server.
*/
let redirect_uri!: string
/**
* In order to take full advantage of DPoP you shall generate a random private key for every
* session. In the browser environment you shall use IndexedDB to persist the generated
* CryptoKeyPair.
*/
let DPoPKeys!: oauth.CryptoKeyPair
/**
* A key that the client has pre-registered at the Authorization Server for use with Private Key JWT
* client authentication method.
*/
let clientPrivateKey!: oauth.CryptoKey
/**
* A key that is pre-registered at the Authorization Server that the client is supposed to sign its
* Request Objects with.
*/
let jarPrivateKey!: oauth.CryptoKey
// End of prerequisites
const as = await oauth
.discoveryRequest(issuer, { algorithm })
.then((response) => oauth.processDiscoveryResponse(issuer, response))
const client: oauth.Client = { client_id }
const clientAuth = oauth.PrivateKeyJwt(clientPrivateKey)
const DPoP = oauth.DPoP(client, DPoPKeys)
const code_challenge_method = 'S256'
/**
* The following MUST be generated for every redirect to the authorization_endpoint. You must store
* the code_verifier in the end-user session such that it can be recovered as the user gets
* redirected from the authorization server back to your application.
*/
const code_verifier = oauth.generateRandomCodeVerifier()
const code_challenge = await oauth.calculatePKCECodeChallenge(code_verifier)
// Signed Request Object (JAR)
let request: string
{
const params = new URLSearchParams()
params.set('client_id', client.client_id)
params.set('code_challenge', code_challenge)
params.set('code_challenge_method', code_challenge_method)
params.set('redirect_uri', redirect_uri)
params.set('response_type', 'code')
params.set('scope', 'openid api:read')
params.set('response_mode', 'jwt')
request = await oauth.issueRequestObject(as, client, params, jarPrivateKey)
}
// Pushed Authorization Request & Response (PAR)
let request_uri: string
{
const params = new URLSearchParams()
params.set('client_id', client.client_id)
params.set('request', request)
const pushedAuthorizationRequest = () =>
oauth.pushedAuthorizationRequest(as, client, clientAuth, params, {
DPoP,
})
let response = await pushedAuthorizationRequest()
const processPushedAuthorizationResponse = () =>
oauth.processPushedAuthorizationResponse(as, client, response)
let result = await processPushedAuthorizationResponse().catch(async (err) => {
if (oauth.isDPoPNonceError(err)) {
// the AS-signalled nonce is now cached, retrying
response = await pushedAuthorizationRequest()
return processPushedAuthorizationResponse()
}
throw err
})
;({ request_uri } = result)
}
{
// redirect user to as.authorization_endpoint
const authorizationUrl = new URL(as.authorization_endpoint!)
authorizationUrl.searchParams.set('client_id', client.client_id)
authorizationUrl.searchParams.set('request_uri', request_uri)
// now redirect the user to authorizationUrl.href
}
// one eternity later, the user lands back on the redirect_uri
// Authorization Code Grant Request & Response
let access_token: string
{
const currentUrl: URL = getCurrentUrl()
const params = await oauth.validateJwtAuthResponse(as, client, currentUrl)
const authorizationCodeGrantRequest = () =>
oauth.authorizationCodeGrantRequest(
as,
client,
clientAuth,
params,
redirect_uri,
code_verifier,
{ DPoP },
)
let response = await authorizationCodeGrantRequest()
const processAuthorizationCodeResponse = () =>
oauth.processAuthorizationCodeResponse(as, client, response, { requireIdToken: true })
let result = await processAuthorizationCodeResponse().catch(async (err) => {
if (oauth.isDPoPNonceError(err)) {
// the AS-signalled nonce is now cached, retrying
response = await authorizationCodeGrantRequest()
return processAuthorizationCodeResponse()
}
throw err
})
// Check ID Token signature for non-repudiation purposes
await oauth.validateApplicationLevelSignature(as, response)
console.log('Access Token Response', result)
;({ access_token } = result)
}
// Protected Resource Request
{
const protectedResourceRequest = () =>
oauth.protectedResourceRequest(
access_token,
'GET',
new URL('https://rs.example.com/api'),
undefined,
undefined,
{ DPoP },
)
let response = await protectedResourceRequest().catch((err) => {
if (oauth.isDPoPNonceError(err)) {
// the RS-signalled nonce is now cached, retrying
return protectedResourceRequest()
}
throw err
})
console.log('Protected Resource Response', await response.json())
}