Skip to content

Commit 4f65365

Browse files
authored
feat: allow skipping consent for trusted clients (#3451)
This adds a new boolean parameter `skip_consent` to the admin APIs of the OAuth clients. This parameter will be forwarded to the consent app as `client.skip_consent`. It is up to the consent app to act on this parameter, but the canonical implementation accepts the consent on the user's behalf, similar to when `skip` is set.
1 parent 023167d commit 4f65365

File tree

58 files changed

+452
-37
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+452
-37
lines changed

client/.snapshots/TestHandler-common-case=create_clients-case=0-description=basic_dynamic_client_registration.json

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"token_endpoint_auth_method": "client_secret_basic",
2121
"userinfo_signed_response_alg": "none",
2222
"metadata": {},
23+
"skip_consent": false,
2324
"authorization_code_grant_access_token_lifespan": null,
2425
"authorization_code_grant_id_token_lifespan": null,
2526
"authorization_code_grant_refresh_token_lifespan": null,

client/.snapshots/TestHandler-common-case=create_clients-case=1-description=basic_admin_registration.json

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"metadata": {
2424
"foo": "bar"
2525
},
26+
"skip_consent": false,
2627
"authorization_code_grant_access_token_lifespan": null,
2728
"authorization_code_grant_id_token_lifespan": null,
2829
"authorization_code_grant_refresh_token_lifespan": null,
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
22
"error": "invalid_client_metadata",
3-
"error_description": "The value of one of the Client Metadata fields is invalid and the server has rejected this request. Note that an Authorization Server MAY choose to substitute a valid value for any requested parameter of a Client's Metadata. metadata cannot be set for dynamic client registration'"
3+
"error_description": "The value of one of the Client Metadata fields is invalid and the server has rejected this request. Note that an Authorization Server MAY choose to substitute a valid value for any requested parameter of a Client's Metadata. 'metadata' cannot be set for dynamic client registration"
44
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"error": "invalid_request",
3+
"error_description": "'skip_consent' cannot be set for dynamic client registration"
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"client_name": "",
3+
"client_secret": "2SKZkBf2P5g4toAXXnCrr~_sDM",
4+
"redirect_uris": [
5+
"http://localhost:3000/cb"
6+
],
7+
"grant_types": null,
8+
"response_types": null,
9+
"scope": "offline_access offline openid",
10+
"audience": [],
11+
"owner": "",
12+
"policy_uri": "",
13+
"allowed_cors_origins": [],
14+
"tos_uri": "",
15+
"client_uri": "",
16+
"logo_uri": "",
17+
"contacts": null,
18+
"client_secret_expires_at": 0,
19+
"subject_type": "public",
20+
"jwks": {},
21+
"token_endpoint_auth_method": "client_secret_basic",
22+
"userinfo_signed_response_alg": "none",
23+
"metadata": {},
24+
"skip_consent": true,
25+
"authorization_code_grant_access_token_lifespan": null,
26+
"authorization_code_grant_id_token_lifespan": null,
27+
"authorization_code_grant_refresh_token_lifespan": null,
28+
"client_credentials_grant_access_token_lifespan": null,
29+
"implicit_grant_access_token_lifespan": null,
30+
"implicit_grant_id_token_lifespan": null,
31+
"jwt_bearer_grant_access_token_lifespan": null,
32+
"refresh_token_grant_id_token_lifespan": null,
33+
"refresh_token_grant_access_token_lifespan": null,
34+
"refresh_token_grant_refresh_token_lifespan": null
35+
}

client/.snapshots/TestHandler-common-case=create_clients-case=7-description=empty_ID_succeeds.json renamed to client/.snapshots/TestHandler-common-case=create_clients-case=9-description=empty_ID_succeeds.json

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"token_endpoint_auth_method": "client_secret_basic",
2222
"userinfo_signed_response_alg": "none",
2323
"metadata": {},
24+
"skip_consent": false,
2425
"authorization_code_grant_access_token_lifespan": null,
2526
"authorization_code_grant_id_token_lifespan": null,
2627
"authorization_code_grant_refresh_token_lifespan": null,

client/.snapshots/TestHandler-common-case=fetching_existing_client-endpoint=admin.json

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"token_endpoint_auth_method": "client_secret_basic",
2222
"userinfo_signed_response_alg": "none",
2323
"metadata": {},
24+
"skip_consent": false,
2425
"authorization_code_grant_access_token_lifespan": null,
2526
"authorization_code_grant_id_token_lifespan": null,
2627
"authorization_code_grant_refresh_token_lifespan": null,

client/.snapshots/TestHandler-common-case=fetching_existing_client-endpoint=selfservice.json

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"jwks": {},
2121
"token_endpoint_auth_method": "client_secret_basic",
2222
"userinfo_signed_response_alg": "none",
23+
"skip_consent": false,
2324
"authorization_code_grant_access_token_lifespan": null,
2425
"authorization_code_grant_id_token_lifespan": null,
2526
"authorization_code_grant_refresh_token_lifespan": null,

client/.snapshots/TestHandler-common-case=update_the_lifespans_of_an_OAuth2_client.json

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"token_endpoint_auth_method": "client_secret_basic",
2222
"userinfo_signed_response_alg": "none",
2323
"metadata": {},
24+
"skip_consent": false,
2425
"authorization_code_grant_access_token_lifespan": "31h0m0s",
2526
"authorization_code_grant_id_token_lifespan": "32h0m0s",
2627
"authorization_code_grant_refresh_token_lifespan": "33h0m0s",

client/.snapshots/TestHandler-common-case=updating_existing_client-endpoint=admin.json

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"token_endpoint_auth_method": "client_secret_basic",
2424
"userinfo_signed_response_alg": "none",
2525
"metadata": {},
26+
"skip_consent": false,
2627
"authorization_code_grant_access_token_lifespan": null,
2728
"authorization_code_grant_id_token_lifespan": null,
2829
"authorization_code_grant_refresh_token_lifespan": null,

client/.snapshots/TestHandler-common-case=updating_existing_client-endpoint=dynamic_client_registration.json

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"token_endpoint_auth_method": "client_secret_basic",
2323
"userinfo_signed_response_alg": "none",
2424
"metadata": {},
25+
"skip_consent": false,
2526
"authorization_code_grant_access_token_lifespan": null,
2627
"authorization_code_grant_id_token_lifespan": null,
2728
"authorization_code_grant_refresh_token_lifespan": null,
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"body": {
33
"error": "invalid_client_metadata",
4-
"error_description": "The value of one of the Client Metadata fields is invalid and the server has rejected this request. Note that an Authorization Server MAY choose to substitute a valid value for any requested parameter of a Client's Metadata. metadata cannot be set for dynamic client registration'"
4+
"error_description": "The value of one of the Client Metadata fields is invalid and the server has rejected this request. Note that an Authorization Server MAY choose to substitute a valid value for any requested parameter of a Client's Metadata. 'metadata' cannot be set for dynamic client registration"
55
},
66
"status": 400
77
}

client/.snapshots/TestHandler-create_client_registration_tokens-case=0-dynamic=true.json

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"subject_type": "",
1717
"jwks": {},
1818
"metadata": {},
19+
"skip_consent": false,
1920
"authorization_code_grant_access_token_lifespan": null,
2021
"authorization_code_grant_id_token_lifespan": null,
2122
"authorization_code_grant_refresh_token_lifespan": null,

client/.snapshots/TestHandler-create_client_registration_tokens-case=1-dynamic=false.json

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"subject_type": "",
1717
"jwks": {},
1818
"metadata": {},
19+
"skip_consent": false,
1920
"authorization_code_grant_access_token_lifespan": null,
2021
"authorization_code_grant_id_token_lifespan": null,
2122
"authorization_code_grant_refresh_token_lifespan": null,

client/.snapshots/TestHandler-create_client_registration_tokens-case=2-dynamic=false.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"subject_type": "",
1818
"jwks": {},
1919
"metadata": {},
20+
"skip_consent": false,
2021
"authorization_code_grant_access_token_lifespan": null,
2122
"authorization_code_grant_id_token_lifespan": null,
2223
"authorization_code_grant_refresh_token_lifespan": null,

client/client.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
"github.com/gobuffalo/pop/v6"
1313
"github.com/gofrs/uuid"
1414

15-
jose "gopkg.in/square/go-jose.v2" // Naming the dependency jose is important for go-swagger to work, see https://github.com/go-swagger/go-swagger/issues/1587
15+
"gopkg.in/square/go-jose.v2" // Naming the dependency jose is important for go-swagger to work, see https://github.com/go-swagger/go-swagger/issues/1587
1616

1717
"github.com/ory/fosite"
1818
"github.com/ory/hydra/v2/x"
@@ -291,6 +291,10 @@ type Client struct {
291291
// RegistrationClientURI is the URL used to update, get, or delete the OAuth2 Client.
292292
RegistrationClientURI string `json:"registration_client_uri,omitempty" db:"-"`
293293

294+
// SkipConsent skips the consent screen for this client. This field can only
295+
// be set from the admin API.
296+
SkipConsent bool `json:"skip_consent" db:"skip_consent" faker:"-"`
297+
294298
Lifespans
295299
}
296300

client/error.go

+6
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,9 @@ var ErrInvalidRedirectURI = &fosite.RFC6749Error{
2020
ErrorField: "invalid_redirect_uri",
2121
CodeField: http.StatusBadRequest,
2222
}
23+
24+
var ErrInvalidRequest = &fosite.RFC6749Error{
25+
DescriptionField: "The request is missing a required parameter, includes an unsupported parameter value (other than grant type), repeats a parameter, includes multiple credentials, utilizes more than one mechanism for authenticating the client, or is otherwise malformed.",
26+
ErrorField: "invalid_request",
27+
CodeField: http.StatusBadRequest,
28+
}

client/handler_test.go

+19
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,25 @@ func TestHandler(t *testing.T) {
328328
path: client.ClientsHandlerPath,
329329
statusCode: http.StatusBadRequest,
330330
},
331+
{
332+
d: "setting skip_consent fails for dynamic registration",
333+
payload: &client.Client{
334+
RedirectURIs: []string{"http://localhost:3000/cb"},
335+
SkipConsent: true,
336+
},
337+
path: client.DynClientsHandlerPath,
338+
statusCode: http.StatusBadRequest,
339+
},
340+
{
341+
d: "setting skip_consent suceeds for admin registration",
342+
payload: &client.Client{
343+
RedirectURIs: []string{"http://localhost:3000/cb"},
344+
SkipConsent: true,
345+
Secret: "2SKZkBf2P5g4toAXXnCrr~_sDM",
346+
},
347+
path: client.ClientsHandlerPath,
348+
statusCode: http.StatusCreated,
349+
},
331350
{
332351
d: "basic dynamic client registration",
333352
payload: &client.Client{

client/sdk_test.go

+13-12
Original file line numberDiff line numberDiff line change
@@ -38,27 +38,28 @@ import (
3838

3939
func createTestClient(prefix string) hydra.OAuth2Client {
4040
return hydra.OAuth2Client{
41-
ClientName: pointerx.String(prefix + "name"),
42-
ClientSecret: pointerx.String(prefix + "secret"),
43-
ClientUri: pointerx.String(prefix + "uri"),
41+
ClientName: pointerx.Ptr(prefix + "name"),
42+
ClientSecret: pointerx.Ptr(prefix + "secret"),
43+
ClientUri: pointerx.Ptr(prefix + "uri"),
4444
Contacts: []string{prefix + "peter", prefix + "pan"},
4545
GrantTypes: []string{prefix + "client_credentials", prefix + "authorize_code"},
46-
LogoUri: pointerx.String(prefix + "logo"),
47-
Owner: pointerx.String(prefix + "an-owner"),
48-
PolicyUri: pointerx.String(prefix + "policy-uri"),
49-
Scope: pointerx.String(prefix + "foo bar baz"),
50-
TosUri: pointerx.String(prefix + "tos-uri"),
46+
LogoUri: pointerx.Ptr(prefix + "logo"),
47+
Owner: pointerx.Ptr(prefix + "an-owner"),
48+
PolicyUri: pointerx.Ptr(prefix + "policy-uri"),
49+
Scope: pointerx.Ptr(prefix + "foo bar baz"),
50+
TosUri: pointerx.Ptr(prefix + "tos-uri"),
5151
ResponseTypes: []string{prefix + "id_token", prefix + "code"},
5252
RedirectUris: []string{"https://" + prefix + "redirect-url", "https://" + prefix + "redirect-uri"},
53-
ClientSecretExpiresAt: pointerx.Int64(0),
54-
TokenEndpointAuthMethod: pointerx.String("client_secret_basic"),
55-
UserinfoSignedResponseAlg: pointerx.String("none"),
56-
SubjectType: pointerx.String("public"),
53+
ClientSecretExpiresAt: pointerx.Ptr[int64](0),
54+
TokenEndpointAuthMethod: pointerx.Ptr("client_secret_basic"),
55+
UserinfoSignedResponseAlg: pointerx.Ptr("none"),
56+
SubjectType: pointerx.Ptr("public"),
5757
Metadata: map[string]interface{}{"foo": "bar"},
5858
// because these values are not nullable in the SQL schema, we have to set them not nil
5959
AllowedCorsOrigins: []string{},
6060
Audience: []string{},
6161
Jwks: map[string]interface{}{},
62+
SkipConsent: pointerx.Ptr(false),
6263
}
6364
}
6465

client/validator.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,12 @@ func (v *Validator) Validate(ctx context.Context, c *Client) error {
179179
func (v *Validator) ValidateDynamicRegistration(ctx context.Context, c *Client) error {
180180
if c.Metadata != nil {
181181
return errorsx.WithStack(ErrInvalidClientMetadata.
182-
WithHint(`metadata cannot be set for dynamic client registration'`),
182+
WithHint(`"metadata" cannot be set for dynamic client registration`),
183183
)
184184
}
185+
if c.SkipConsent {
186+
return errorsx.WithStack(ErrInvalidRequest.WithDescription(`"skip_consent" cannot be set for dynamic client registration`))
187+
}
185188

186189
return v.Validate(ctx, c)
187190
}

cmd/.snapshots/TestCreateClient-case=creates_successfully.json

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"code"
1616
],
1717
"scope": "offline_access offline openid",
18+
"skip_consent": false,
1819
"subject_type": "public",
1920
"token_endpoint_auth_method": "client_secret_basic",
2021
"tos_uri": "",

cmd/.snapshots/TestCreateClient-case=supports_encryption.json

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"code"
2222
],
2323
"scope": "offline_access offline openid",
24+
"skip_consent": false,
2425
"subject_type": "public",
2526
"token_endpoint_auth_method": "client_secret_basic",
2627
"tos_uri": "",

cmd/.snapshots/TestCreateClient-case=supports_setting_flags.json

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"code"
2222
],
2323
"scope": "offline_access offline openid",
24+
"skip_consent": false,
2425
"subject_type": "public",
2526
"token_endpoint_auth_method": "client_secret_basic",
2627
"tos_uri": "",

cmd/.snapshots/TestGetClient-case=gets_client.json

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"owner": "",
99
"policy_uri": "",
1010
"scope": "",
11+
"skip_consent": false,
1112
"subject_type": "",
1213
"token_endpoint_auth_method": "client_secret_post",
1314
"tos_uri": ""

cmd/.snapshots/TestGetClient-case=gets_multiple_clients.json

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"redirect_uris": [],
1616
"response_types": [],
1717
"scope": "",
18+
"skip_consent": false,
1819
"subject_type": "",
1920
"token_endpoint_auth_method": "client_secret_post",
2021
"tos_uri": ""
@@ -35,6 +36,7 @@
3536
"redirect_uris": [],
3637
"response_types": [],
3738
"scope": "",
39+
"skip_consent": false,
3840
"subject_type": "",
3941
"token_endpoint_auth_method": "client_secret_post",
4042
"tos_uri": ""

cmd/.snapshots/TestImportClient-case=imports_clients_from_single_file.json

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"owner": "",
1212
"policy_uri": "",
1313
"scope": "foo",
14+
"skip_consent": false,
1415
"subject_type": "public",
1516
"token_endpoint_auth_method": "client_secret_basic",
1617
"tos_uri": "",
@@ -28,6 +29,7 @@
2829
"owner": "",
2930
"policy_uri": "",
3031
"scope": "bar",
32+
"skip_consent": false,
3133
"subject_type": "public",
3234
"token_endpoint_auth_method": "client_secret_basic",
3335
"tos_uri": "",

cmd/.snapshots/TestImportClient-case=performs_appropriate_error_reporting.json

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"owner": "",
1212
"policy_uri": "",
1313
"scope": "foo",
14+
"skip_consent": false,
1415
"subject_type": "public",
1516
"token_endpoint_auth_method": "client_secret_basic",
1617
"tos_uri": "",
@@ -28,6 +29,7 @@
2829
"owner": "",
2930
"policy_uri": "",
3031
"scope": "bar",
32+
"skip_consent": false,
3133
"subject_type": "public",
3234
"token_endpoint_auth_method": "client_secret_basic",
3335
"tos_uri": "",

cmd/.snapshots/TestUpdateClient-case=creates_successfully.json

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"code"
1616
],
1717
"scope": "offline_access offline openid",
18+
"skip_consent": false,
1819
"subject_type": "public",
1920
"token_endpoint_auth_method": "client_secret_basic",
2021
"tos_uri": "",

cmd/.snapshots/TestUpdateClient-case=supports_encryption.json

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"code"
1616
],
1717
"scope": "offline_access offline openid",
18+
"skip_consent": false,
1819
"subject_type": "public",
1920
"token_endpoint_auth_method": "client_secret_basic",
2021
"tos_uri": "",

cypress/integration/oauth2/authorize_code.js

+23-1
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
import { prng } from "../../helpers"
55

66
describe("The OAuth 2.0 Authorization Code Grant", function () {
7-
const nc = () => ({
7+
const nc = (extradata) => ({
88
client_secret: prng(),
99
scope: "offline_access openid",
1010
subject_type: "public",
1111
token_endpoint_auth_method: "client_secret_basic",
1212
redirect_uris: [`${Cypress.env("client_url")}/oauth2/callback`],
1313
grant_types: ["authorization_code", "refresh_token"],
14+
...extradata,
1415
})
1516

1617
it("should return an Access, Refresh, and ID Token when scope offline_access and openid are granted", function () {
@@ -90,4 +91,25 @@ describe("The OAuth 2.0 Authorization Code Grant", function () {
9091
expect(refresh_token).to.be.undefined
9192
})
9293
})
94+
95+
it("should skip consent if the client is confgured thus", function () {
96+
const client = nc({ skip_consent: true })
97+
cy.authCodeFlow(client, {
98+
consent: { scope: ["offline_access", "openid"], skip: true },
99+
})
100+
101+
cy.get("body")
102+
.invoke("text")
103+
.then((content) => {
104+
const {
105+
result,
106+
token: { access_token, id_token, refresh_token },
107+
} = JSON.parse(content)
108+
109+
expect(result).to.equal("success")
110+
expect(access_token).to.not.be.empty
111+
expect(id_token).to.not.be.empty
112+
expect(refresh_token).to.not.be.empty
113+
})
114+
})
93115
})

0 commit comments

Comments
 (0)