-
-
Notifications
You must be signed in to change notification settings - Fork 126
/
consent.ts
175 lines (154 loc) · 7.07 KB
/
consent.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
164
165
166
167
168
169
170
171
172
173
174
175
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
import express from "express"
import url from "url"
import urljoin from "url-join"
import csrf from "csurf"
import { hydraAdmin } from "../config"
import { oidcConformityMaybeFakeSession } from "./stub/oidc-cert"
import { AcceptOAuth2ConsentRequestSession } from "@ory/client"
// Sets up csrf protection
const csrfProtection = csrf({
cookie: {
sameSite: "lax",
},
})
const router = express.Router()
router.get("/", csrfProtection, (req, res, next) => {
// Parses the URL query
const query = url.parse(req.url, true).query
// The challenge is used to fetch information about the consent request from ORY hydraAdmin.
const challenge = String(query.consent_challenge)
if (!challenge) {
next(new Error("Expected a consent challenge to be set but received none."))
return
}
// This section processes consent requests and either shows the consent UI or
// accepts the consent request right away if the user has given consent to this
// app before
hydraAdmin
.adminGetOAuth2ConsentRequest(challenge)
// This will be called if the HTTP request was successful
.then(({ data: body }) => {
// If a user has granted this application the requested scope, hydra will tell us to not show the UI.
// Any cast needed because the SDK changes are still unreleased.
// TODO: Remove in a later version.
if (body.skip || (body.client as any)?.skip_consent) {
// You can apply logic here, for example grant another scope, or do whatever...
// ...
// Now it's time to grant the consent request. You could also deny the request if something went terribly wrong
return hydraAdmin
.adminAcceptOAuth2ConsentRequest(challenge, {
// We can grant all scopes that have been requested - hydra already checked for us that no additional scopes
// are requested accidentally.
grant_scope: body.requested_scope,
// ORY Hydra checks if requested audiences are allowed by the client, so we can simply echo this.
grant_access_token_audience: body.requested_access_token_audience,
// The session allows us to set session data for id and access tokens
session: {
// This data will be available when introspecting the token. Try to avoid sensitive information here,
// unless you limit who can introspect tokens.
// accessToken: { foo: 'bar' },
// This data will be available in the ID token.
// idToken: { baz: 'bar' },
},
})
.then(({ data: body }) => {
// All we need to do now is to redirect the user back to hydra!
res.redirect(String(body.redirect_to))
})
}
// If consent can't be skipped we MUST show the consent UI.
res.render("consent", {
csrfToken: req.csrfToken(),
challenge: challenge,
// We have a bunch of data available from the response, check out the API docs to find what these values mean
// and what additional data you have available.
requested_scope: body.requested_scope,
user: body.subject,
client: body.client,
action: urljoin(process.env.BASE_URL || "", "/consent"),
})
})
// This will handle any error that happens when making HTTP calls to hydra
.catch(next)
// The consent request has now either been accepted automatically or rendered.
})
router.post("/", csrfProtection, (req, res, next) => {
// The challenge is now a hidden input field, so let's take it from the request body instead
const challenge = req.body.challenge
// Let's see if the user decided to accept or reject the consent request..
if (req.body.submit === "Deny access") {
// Looks like the consent request was denied by the user
return (
hydraAdmin
.adminRejectOAuth2ConsentRequest(challenge, {
error: "access_denied",
error_description: "The resource owner denied the request",
})
.then(({ data: body }) => {
// All we need to do now is to redirect the browser back to hydra!
res.redirect(String(body.redirect_to))
})
// This will handle any error that happens when making HTTP calls to hydra
.catch(next)
)
}
// label:consent-deny-end
let grantScope = req.body.grant_scope
if (!Array.isArray(grantScope)) {
grantScope = [grantScope]
}
// The session allows us to set session data for id and access tokens
let session: AcceptOAuth2ConsentRequestSession = {
// This data will be available when introspecting the token. Try to avoid sensitive information here,
// unless you limit who can introspect tokens.
access_token: {
// foo: 'bar'
},
// This data will be available in the ID token.
id_token: {
// baz: 'bar'
},
}
// Here is also the place to add data to the ID or access token. For example,
// if the scope 'profile' is added, add the family and given name to the ID Token claims:
// if (grantScope.indexOf('profile')) {
// session.id_token.family_name = 'Doe'
// session.id_token.given_name = 'John'
// }
// Let's fetch the consent request again to be able to set `grantAccessTokenAudience` properly.
hydraAdmin
.adminGetOAuth2ConsentRequest(challenge)
// This will be called if the HTTP request was successful
.then(({ data: body }) => {
return hydraAdmin
.adminAcceptOAuth2ConsentRequest(challenge, {
// We can grant all scopes that have been requested - hydra already checked for us that no additional scopes
// are requested accidentally.
grant_scope: grantScope,
// If the environment variable CONFORMITY_FAKE_CLAIMS is set we are assuming that
// the app is built for the automated OpenID Connect Conformity Test Suite. You
// can peak inside the code for some ideas, but be aware that all data is fake
// and this only exists to fake a login system which works in accordance to OpenID Connect.
//
// If that variable is not set, the session will be used as-is.
session: oidcConformityMaybeFakeSession(grantScope, body, session),
// ORY Hydra checks if requested audiences are allowed by the client, so we can simply echo this.
grant_access_token_audience: body.requested_access_token_audience,
// This tells hydra to remember this consent request and allow the same client to request the same
// scopes from the same user, without showing the UI, in the future.
remember: Boolean(req.body.remember),
// When this "remember" sesion expires, in seconds. Set this to 0 so it will never expire.
remember_for: 3600,
})
.then(({ data: body }) => {
// All we need to do now is to redirect the user back to hydra!
res.redirect(String(body.redirect_to))
})
})
// This will handle any error that happens when making HTTP calls to hydra
.catch(next)
// label:docs-accept-consent
})
export default router