rodauth-oauth 1.1.0 → 1.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 12c86242a8a2001fba629cb6bd8e25886b8805fce5d0965ebc70377824e25e91
4
- data.tar.gz: 2fdc78f81b737c9c0d0086f258a86807f8855d3f0bc9be89bffb6d6a90946ed3
3
+ metadata.gz: 4d7d5f8b68686703954bf4e335cef0ea33f9e31c94c439df84f08e8ff3270829
4
+ data.tar.gz: 1da57ba2082818a74dbca4d1c6bcab0c15f97da891e12c03a8bf91440a4edcfd
5
5
  SHA512:
6
- metadata.gz: be15b77c46a135d213cd6e6e6bc7000b961e036febdb8f75bb922f09554d68ab757d5094fe96816c83c5966a3094daa32ecbbee798b2a8bb72417df9506ac3b3
7
- data.tar.gz: a5fec610e9193d449ef49fbfde36688398c9e4e576da5ea579165c306ecc51bc910d0d758c923a9bece59ef4e7651347f9ebb7951504f3354b101fe5f845173a
6
+ metadata.gz: 8230b54e51d2081e25d1386d6294745d54eebbe11a6677bdb9cade14e0a418658bc2b8a67ae2e6355458f4b43d8a2df1700cd3e0496fa8a10e690318f3d03ba0
7
+ data.tar.gz: 31ab5721a6464b751860b6896f47999e189592582842ba419ab0a057ff38af98612d54a8b00177092e2fe5993af1e5554cecafbbfaab18a495656117f19ce4fd
data/README.md CHANGED
@@ -17,6 +17,7 @@ This is an extension to the `rodauth` gem which implements the [OAuth 2.0 framew
17
17
  * Config OP
18
18
  * Dynamic OP
19
19
  * Form Post OP
20
+ * 3rd Party-Init OP
20
21
 
21
22
  (it also passes the conformance tests for the RP-Initiated Logout OP).
22
23
 
@@ -24,7 +25,7 @@ This is an extension to the `rodauth` gem which implements the [OAuth 2.0 framew
24
25
 
25
26
  This gem implements the following RFCs and features of OAuth:
26
27
 
27
- * `oauth` - [The OAuth 2.0 protocol framework](https://tools.ietf.org/html/rfc6749):
28
+ * `oauth` - [The OAuth 2.0 protocol framework](-/wikis/home#oauth-20-protocol-framework):
28
29
  * [Access Token generation](https://tools.ietf.org/html/rfc6749#section-1.4);
29
30
  * [Access Token refresh token grant](https://tools.ietf.org/html/rfc6749#section-1.5);
30
31
  * `oauth_authorization_code_grant` - [Authorization code grant](https://tools.ietf.org/html/rfc6749#section-1.3);
@@ -33,10 +34,13 @@ This gem implements the following RFCs and features of OAuth:
33
34
  * `oauth_device_code_grant` - [Device code grant (off by default)](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-device-flow-15);
34
35
  * `oauth_token_revocation` - [Token revocation](https://tools.ietf.org/html/rfc7009);
35
36
  * `oauth_token_introspection` - [Token introspection](https://tools.ietf.org/html/rfc7662);
37
+ * `oauth_pushed_authorization_request` - [Pushed Authorization Request](https://datatracker.ietf.org/doc/html/rfc9126);
36
38
  * [Authorization Server Metadata](https://tools.ietf.org/html/rfc8414);
37
39
  * `oauth_pkce` - [PKCE](https://tools.ietf.org/html/rfc7636);
40
+ * `oauth_tls_client_auth` - [Mutual-TLS Client Authentication](https://datatracker.ietf.org/doc/html/rfc8705);
38
41
  * `oauth_jwt` - [JWT Access Tokens](https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-07);
39
42
  * `oauth_jwt_secured_authorization_request` - [JWT Secured Authorization Request](https://tools.ietf.org/html/draft-ietf-oauth-jwsreq-20);
43
+ * `oauth_jwt_secured_authorization_response_mode` - [JWT Secured Authorization Response_mode](https://openid.net/specs/openid-financial-api-jarm.html);
40
44
  * `oauth_resource_indicators` - [Resource Indicators](https://datatracker.ietf.org/doc/html/rfc8707);
41
45
  * Access Type (Token refresh online and offline);
42
46
  * `oauth_http_mac` - [MAC Authentication Scheme](https://tools.ietf.org/html/draft-hammer-oauth-v2-mac-token-02);
@@ -44,18 +48,17 @@ This gem implements the following RFCs and features of OAuth:
44
48
  * `oauth_saml_bearer_grant` - [SAML 2.0 Bearer Assertion](https://datatracker.ietf.org/doc/html/rfc7522);
45
49
  * `oauth_jwt_bearer_grant` - [JWT Bearer Assertion](https://datatracker.ietf.org/doc/html/rfc7523);
46
50
 
47
- * `oauth_dynamic_client_registration` - [Dynamic Client Registration Protocol](https://datatracker.ietf.org/doc/html/rfc7591);
51
+ * `oauth_dynamic_client_registration` - [Dynamic Client Registration Protocol](https://datatracker.ietf.org/doc/html/rfc7591) and [Dynamic Client Registration Management](https://www.rfc-editor.org/rfc/rfc7592);
48
52
  * OAuth application and token management dashboards;
49
53
  * The recommendations for [Native Apps](https://www.rfc-editor.org/rfc/rfc8252);
50
54
 
51
55
  It also implements the [OpenID Connect layer](https://openid.net/connect/) (via the `openid` feature) on top of the OAuth features it provides, including:
52
56
 
53
- * `oidc`
54
- * [OpenID Connect Core](https://openid.net/specs/openid-connect-core-1_0.html);
55
- * [OpenID Connect Discovery](https://openid.net/specs/openid-connect-discovery-1_0-29.html);
56
- * [OpenID Multiple Response Types](https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html);
57
- * `oidc_dynamic_client_registration` - [OpenID Connect Dynamic Client Registration](https://openid.net/specs/openid-connect-registration-1_0.html);
58
- * `oidc_rp_initiated_logout` - [RP Initiated Logout](https://openid.net/specs/openid-connect-rpinitiated-1_0.html);
57
+ * [OpenID Connect Core](https://gitlab.com/os85/rodauth-oauth/-/wikis/Id-Token-Authentication);
58
+ * [OpenID Connect Discovery](https://gitlab.com/os85/rodauth-oauth/-/wikis/OIDC-Dynamic-Client-Registration);
59
+ * [OpenID Multiple Response Types](https://gitlab.com/os85/rodauth-oauth/-/wikis/Hybrid-flow);
60
+ * [OpenID Connect Dynamic Client Registration](https://gitlab.com/os85/rodauth-oauth/-/wikis/OIDC-Dynamic-Client-Registration);
61
+ * [RP Initiated Logout](https://gitlab.com/os85/rodauth-oauth/-/wikis/RP-Initiated-Logout);
59
62
 
60
63
  This gem supports also rails (through [rodauth-rails]((https://github.com/janko/rodauth-rails))).
61
64
 
@@ -1,4 +1,4 @@
1
- ## 1.0.0 (10/01/2023)
1
+ ## 1.1.0 (10/01/2023)
2
2
 
3
3
  ## Features
4
4
 
@@ -0,0 +1,36 @@
1
+ ## 1.2.0 (13/02/2023)
2
+
3
+ ### Features
4
+
5
+ #### Pushed Authorization Requests (PAR)
6
+
7
+ RFC: https://datatracker.ietf.org/doc/html/rfc9126
8
+
9
+ `rodauth-oauth` supports Pushed Authorization Requests, via the `:oauth_pushed_authorization_request` feature.
10
+
11
+ More info about the feature [in the wiki](https://gitlab.com/os85/rodauth-oauth/-/wikis/Pushed-Authorization-Requests).
12
+
13
+ #### mTLS Client Auth (+ certificate-bound access tokens)
14
+
15
+ RFC: https://www.rfc-editor.org/rfc/rfc8705
16
+
17
+ The `:oauth_tls_client_auth` feature adds support for the variants of mTLS Client Authentication "PKI Mutual-TLS Method" and 2Self-Signed Certificate Mutual-TLS Method". It also supports client certificate bound access tokens.
18
+
19
+ More about it [in the wiki](https://gitlab.com/os85/rodauth-oauth/-/wikis/mTLS-Client-Authentication).
20
+
21
+ #### Dynamic Client Registration management
22
+
23
+ RFC: https://www.rfc-editor.org/rfc/rfc7592
24
+
25
+ Support for dynamci client registration management was added to the `:oauth_dynamic_client_registration` feature.
26
+
27
+ More info about it [in the wiki](https://gitlab.com/os85/rodauth-oauth/-/wikis/Dynamic-Client-Registration#getputdelete-registerclient_id).
28
+
29
+ ### Improvements
30
+
31
+ * Support for 3rd-party initiated login was added, by including support for the `initiate_login_uri` attribute in the register route from the `:oauth_dynamic_client_registration` feature.
32
+ * Support for multitenant resource ownership was added, here's a [description from the wiki](https://gitlab.com/os85/rodauth-oauth/-/wikis/How-to#scoping-grants-from-the-same-resource-owner).
33
+
34
+ ### Bugfixes
35
+
36
+ * oidc: userinfo claims were not including claims with value `false`, such as `"email_verified"`. This behaviour has been fixed, and only claims of value `null` are omitted.
@@ -0,0 +1,38 @@
1
+ ## 1.3.0 (02/04/2023)
2
+
3
+ ## Features
4
+
5
+ ### Self-Signed Issued Tokens
6
+
7
+ `rodauth-oauth` supports self-signed issued tokens, via the `oidc_self_issued` feature.
8
+
9
+ More info about the feature [in the docs](https://gitlab.com/os85/rodauth-oauth/-/wikis/Self-Issued-OpenID).
10
+
11
+ #### JARM
12
+
13
+ `rodauth-oauth` supports JWT-secured Authorization Response Mode, also known as JARM, via the `oauth_jwt_secured_authorization_response_mode`.
14
+
15
+ More info about the feature [in the docs](https://gitlab.com/os85/rodauth-oauth/-/wikis/JWT-Secured-Authorization-Response-Mode).
16
+
17
+ ## Improvements
18
+
19
+ ### `fill_with_account_claims` auth method
20
+
21
+ `fill_with_account_claims` is now exposed as an auth method. This allows one to override to be able to cover certain requirements, such as aggregated and distributed claims. Here's a [link to the docs](https://gitlab.com/os85/rodauth-oauth/-/wikis/Id-Token-Authentication#claim-types) explaining how to do it.
22
+
23
+ ### oidc: only generate refresh token when `offline_access` scope is used.
24
+
25
+ When the `oidc` feature is used, refresh tokens won't be generated anymore by default; in order to do so, the `offline_access` needs to be requested for in the respective authorization request, [as the spec mandates](https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess).
26
+
27
+ ### oidc: implicit grant loaded by default
28
+
29
+ The `oidc` feature now loads the `oauth_implicit_grant` feature by default. This hadn't been done before due to the wish to ship a secure integration by default, but since then, spec compliance became more prioritary, and this is a requirement.
30
+
31
+ ## Bugfixes
32
+
33
+ * rails integration: activerecord migrations fixes:
34
+ * use `bigint` for foreign keys;
35
+ * index creation instruction with the wrong syntax;
36
+ * set precision 6 for default timestamps, to comply with AR defaults;
37
+ * add missing `code` column to the `oauth_pushed_requests` table;
38
+ * oidc: when using the `id_token` , or any composite response type including `id_token`, using any response mode other than `fragment` will result in an invalid request.
@@ -75,6 +75,9 @@
75
75
  <% if params[:acr_values] %>
76
76
  <%= hidden_field_tag :acr_values, params[:acr_values] %>
77
77
  <% end %>
78
+ <% if params[:registration] %>
79
+ <%= hidden_field_tag :registration, params[:registration] %>
80
+ <% end %>
78
81
  <% end %>
79
82
  </div>
80
83
  <p class="text-center">
@@ -1,7 +1,7 @@
1
1
  class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
2
2
  def change
3
3
  create_table :oauth_applications do |t|
4
- t.integer :account_id
4
+ t.bigint :account_id
5
5
  t.foreign_key :accounts, column: :account_id
6
6
  t.string :name, null: false
7
7
  t.string :description, null: true
@@ -9,8 +9,9 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
9
9
  t.string :redirect_uri, null: false
10
10
  t.string :client_id, null: false, index: { unique: true }
11
11
  t.string :client_secret, null: false, index: { unique: true }
12
+ t.string :registration_access_token, null: true
12
13
  t.string :scopes, null: false
13
- t.datetime :created_at, null: false, default: -> { "CURRENT_TIMESTAMP" }
14
+ t.datetime :created_at, null: false, default: -> { "CURRENT_TIMESTAMP(6)" }
14
15
 
15
16
  # :oauth_dynamic_client_configuration enabled, extra optional params
16
17
  t.string :token_endpoint_auth_method, null: true
@@ -29,6 +30,7 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
29
30
  # :oidc_dynamic_client_configuration enabled, extra optional params
30
31
  t.string :sector_identifier_uri, null: true
31
32
  t.string :application_type, null: true
33
+ t.string :initiate_login_uri, null: true
32
34
 
33
35
  # :oidc enabled
34
36
  t.string :subject_type, null: true
@@ -44,26 +46,35 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
44
46
  t.string :request_object_encryption_alg, null: true
45
47
  t.string :request_object_encryption_enc, null: true
46
48
  t.string :request_uris, null: true
49
+ t.boolean :require_pushed_authorization_requests, null: false, default: false
50
+
51
+ # :oauth_tls_client_auth
52
+ t.string :tls_client_auth_subject_dn, null: true
53
+ t.string :tls_client_auth_san_dns, null: true
54
+ t.string :tls_client_auth_san_uri, null: true
55
+ t.string :tls_client_auth_san_ip, null: true
56
+ t.string :tls_client_auth_san_email, null: true
57
+ t.boolean :tls_client_certificate_bound_access_tokens, default: false
47
58
 
48
59
  # :oidc_rp_initiated_logout enabled
49
60
  t.string :post_logout_redirect_uris, null: false
50
61
  end
51
62
 
52
63
  create_table :oauth_grants do |t|
53
- t.integer :account_id
64
+ t.bigint :account_id
54
65
  t.foreign_key :accounts, column: :account_id
55
- t.integer :oauth_application_id
66
+ t.bigint :oauth_application_id
56
67
  t.foreign_key :oauth_applications, column: :oauth_application_id
57
68
  t.string :type, null: true
58
69
  t.string :code, null: true
59
70
  t.index(%i[oauth_application_id code], unique: true)
60
- t.string :token, unique: true
61
- t.string :refresh_token, unique: true
71
+ t.string :token, index: { unique: true }
72
+ t.string :refresh_token, index: { unique: true }
62
73
  t.datetime :expires_in, null: false
63
74
  t.string :redirect_uri
64
75
  t.datetime :revoked_at
65
76
  t.string :scopes, null: false
66
- t.datetime :created_at, null: false, default: -> { "CURRENT_TIMESTAMP" }
77
+ t.datetime :created_at, null: false, default: -> { "CURRENT_TIMESTAMP(6)" }
67
78
  t.string :access_type, null: false, default: "offline"
68
79
 
69
80
  # :oauth_pkce enabled
@@ -71,9 +82,12 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
71
82
  t.string :code_challenge_method
72
83
 
73
84
  # :oauth_device_code_grant enabled
74
- t.string :user_code, null: true, unique: true
85
+ t.string :user_code, null: true, index: { unique: true }
75
86
  t.datetime :last_polled_at, null: true
76
87
 
88
+ # :oauth_tls_client_auth
89
+ t.string :certificate_thumbprint, null: true
90
+
77
91
  # :resource_indicators enabled
78
92
  t.string :resource
79
93
 
@@ -83,5 +97,14 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
83
97
  t.string :claims_locales
84
98
  t.string :claims
85
99
  end
100
+
101
+ create_table :oauth_pushed_requests do |t|
102
+ t.bigint :oauth_application_id
103
+ t.foreign_key :oauth_applications, column: :oauth_application_id
104
+ t.string :code, null: false, index: { unique: true }
105
+ t.string :params, null: false
106
+ t.datetime :expires_in, null: false
107
+ t.index %i[oauth_application_id code], unique: true
108
+ end
86
109
  end
87
- end
110
+ end
@@ -27,7 +27,19 @@ module Rodauth
27
27
 
28
28
  response_mode = param_or_nil("response_mode")
29
29
 
30
- redirect_response_error("invalid_request") if response_mode && !oauth_response_modes_supported.include?(response_mode)
30
+ return unless response_mode
31
+
32
+ redirect_response_error("invalid_request") unless oauth_response_modes_supported.include?(response_mode)
33
+
34
+ response_type = param_or_nil("response_type")
35
+
36
+ return unless response_type.nil? || response_type == "code"
37
+
38
+ redirect_response_error("invalid_request") unless oauth_response_modes_for_code_supported.include?(response_mode)
39
+ end
40
+
41
+ def oauth_response_modes_for_code_supported
42
+ %w[query form_post]
31
43
  end
32
44
 
33
45
  def validate_token_params
@@ -57,7 +69,7 @@ module Rodauth
57
69
  def _do_authorize_code
58
70
  create_params = {
59
71
  oauth_grants_type_column => "authorization_code",
60
- oauth_grants_account_id_column => account_id
72
+ **resource_owner_params
61
73
  }
62
74
 
63
75
  { "code" => create_oauth_grant(create_params) }
@@ -67,55 +79,65 @@ module Rodauth
67
79
  redirect_url = URI.parse(redirect_uri)
68
80
  case mode
69
81
  when "query"
70
- params = params.map { |k, v| "#{CGI.escape(k)}=#{CGI.escape(v)}" }
82
+ params = [URI.encode_www_form(params)]
71
83
  params << redirect_url.query if redirect_url.query
72
84
  redirect_url.query = params.join("&")
73
85
  redirect(redirect_url.to_s)
74
86
  when "form_post"
75
- scope.view layout: false, inline: <<-FORM
76
- <html>
77
- <head><title>Authorized</title></head>
78
- <body onload="javascript:document.forms[0].submit()">
79
- <form method="post" action="#{redirect_uri}">
80
- #{
81
- params.map do |name, value|
82
- "<input type=\"hidden\" name=\"#{scope.h(name)}\" value=\"#{scope.h(value)}\" />"
83
- end.join
84
- }
85
- <input type="submit" class="btn btn-outline-primary" value="#{scope.h(oauth_authorize_post_button)}"/>
86
- </form>
87
- </body>
88
- </html>
89
- FORM
87
+ inline_html = form_post_response_html(redirect_uri) do
88
+ params.map do |name, value|
89
+ "<input type=\"hidden\" name=\"#{scope.h(name)}\" value=\"#{scope.h(value)}\" />"
90
+ end.join
91
+ end
92
+ scope.view layout: false, inline: inline_html
90
93
  end
91
94
  end
92
95
 
93
- def _redirect_response_error(redirect_url, query_params)
96
+ def _redirect_response_error(redirect_url, params)
94
97
  response_mode = param_or_nil("response_mode") || oauth_response_mode
95
98
 
96
99
  case response_mode
97
100
  when "form_post"
98
101
  response["Content-Type"] = "text/html"
99
- response.write <<-FORM
100
- <html>
101
- <head><title></title></head>
102
- <body onload="javascript:document.forms[0].submit()">
103
- <form method="post" action="#{redirect_uri}">
104
- #{
105
- query_params.map do |name, value|
106
- "<input type=\"hidden\" name=\"#{name}\" value=\"#{scope.h(value)}\" />"
107
- end.join
108
- }
109
- </form>
110
- </body>
111
- </html>
112
- FORM
102
+ error_body = form_post_error_response_html(redirect_url) do
103
+ params.map do |name, value|
104
+ "<input type=\"hidden\" name=\"#{name}\" value=\"#{scope.h(value)}\" />"
105
+ end.join
106
+ end
107
+ response.write(error_body)
113
108
  request.halt
114
109
  else
115
110
  super
116
111
  end
117
112
  end
118
113
 
114
+ def form_post_response_html(url)
115
+ <<-FORM
116
+ <html>
117
+ <head><title>Authorized</title></head>
118
+ <body onload="javascript:document.forms[0].submit()">
119
+ <form method="post" action="#{url}">
120
+ #{yield}
121
+ <input type="submit" class="btn btn-outline-primary" value="#{scope.h(oauth_authorize_post_button)}"/>
122
+ </form>
123
+ </body>
124
+ </html>
125
+ FORM
126
+ end
127
+
128
+ def form_post_error_response_html(url)
129
+ <<-FORM
130
+ <html>
131
+ <head><title></title></head>
132
+ <body onload="javascript:document.forms[0].submit()">
133
+ <form method="post" action="#{url}">
134
+ #{yield}
135
+ </form>
136
+ </body>
137
+ </html>
138
+ FORM
139
+ end
140
+
119
141
  def create_token(grant_type)
120
142
  return super unless supported_grant_type?(grant_type, "authorization_code")
121
143
 
@@ -28,6 +28,11 @@ module Rodauth
28
28
  translatable_method :oauth_unsupported_response_type_message, "Unsupported response type"
29
29
  translatable_method :oauth_authorize_parameter_required, "Invalid or missing '%<parameter>s'"
30
30
 
31
+ auth_value_methods(
32
+ :resource_owner_params,
33
+ :oauth_grants_resource_owner_columns
34
+ )
35
+
31
36
  # /authorize
32
37
  auth_server_route(:authorize) do |r|
33
38
  require_authorizable_account
@@ -73,7 +78,9 @@ module Rodauth
73
78
 
74
79
  if (redirect_uri = param_or_nil("redirect_uri"))
75
80
  normalized_redirect_uri = normalize_redirect_uri_for_comparison(redirect_uri)
76
- redirect_authorize_error("redirect_uri") unless redirect_uris.include?(normalized_redirect_uri)
81
+ unless redirect_uris.include?(normalized_redirect_uri) || redirect_uris.include?(redirect_uri)
82
+ redirect_authorize_error("redirect_uri")
83
+ end
77
84
  elsif redirect_uris.size > 1
78
85
  redirect_authorize_error("redirect_uri")
79
86
  end
@@ -85,6 +92,14 @@ module Rodauth
85
92
  try_approval_prompt if use_oauth_access_type? && request.get?
86
93
 
87
94
  redirect_response_error("invalid_scope") if (request.post? || param_or_nil("scope")) && !check_valid_scopes?
95
+
96
+ response_mode = param_or_nil("response_mode")
97
+
98
+ redirect_response_error("invalid_request") unless response_mode.nil? || oauth_response_modes_supported.include?(response_mode)
99
+ end
100
+
101
+ def check_valid_scopes?(scp = scopes)
102
+ super(scp - %w[offline_access])
88
103
  end
89
104
 
90
105
  def check_valid_response_type?
@@ -109,13 +124,20 @@ module Rodauth
109
124
  !approval_prompt || APPROVAL_PROMPTS.include?(approval_prompt)
110
125
  end
111
126
 
127
+ def resource_owner_params
128
+ { oauth_grants_account_id_column => account_id }
129
+ end
130
+
131
+ def oauth_grants_resource_owner_columns
132
+ [oauth_grants_account_id_column]
133
+ end
134
+
112
135
  def try_approval_prompt
113
136
  approval_prompt = param_or_nil("approval_prompt")
114
137
 
115
138
  return unless approval_prompt && approval_prompt == "auto"
116
139
 
117
- return if db[oauth_grants_table].where(
118
- oauth_grants_account_id_column => account_id,
140
+ return if db[oauth_grants_table].where(resource_owner_params).where(
119
141
  oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
120
142
  oauth_grants_redirect_uri_column => redirect_uri,
121
143
  oauth_grants_scopes_column => scopes.join(oauth_scope_separator),
@@ -762,31 +762,31 @@ module Rodauth
762
762
  throw_json_response_error(status_code, error_code)
763
763
  else
764
764
  redirect_url = URI.parse(redirect_url)
765
- query_params = []
765
+ params = []
766
766
 
767
- query_params << if respond_to?(:"oauth_#{error_code}_error_code")
768
- ["error", send(:"oauth_#{error_code}_error_code")]
769
- else
770
- ["error", error_code]
771
- end
767
+ params << if respond_to?(:"oauth_#{error_code}_error_code")
768
+ ["error", send(:"oauth_#{error_code}_error_code")]
769
+ else
770
+ ["error", error_code]
771
+ end
772
772
 
773
773
  if respond_to?(:"oauth_#{error_code}_message")
774
774
  message = send(:"oauth_#{error_code}_message")
775
- query_params << ["error_description", CGI.escape(message)]
775
+ params << ["error_description", CGI.escape(message)]
776
776
  end
777
777
 
778
778
  state = param_or_nil("state")
779
779
 
780
- query_params << ["state", state] if state
780
+ params << ["state", state] if state
781
781
 
782
- _redirect_response_error(redirect_url, query_params)
782
+ _redirect_response_error(redirect_url, params)
783
783
  end
784
784
  end
785
785
 
786
- def _redirect_response_error(redirect_url, query_params)
787
- query_params = query_params.map { |k, v| "#{k}=#{v}" }
788
- query_params << redirect_url.query if redirect_url.query
789
- redirect_url.query = query_params.join("&")
786
+ def _redirect_response_error(redirect_url, params)
787
+ params = params.map { |k, v| "#{k}=#{v}" }
788
+ params << redirect_url.query if redirect_url.query
789
+ redirect_url.query = params.join("&")
790
790
  redirect(redirect_url.to_s)
791
791
  end
792
792
 
@@ -841,10 +841,10 @@ module Rodauth
841
841
  throw_json_response_error(oauth_authorization_required_error_status, "invalid_client")
842
842
  end
843
843
 
844
- def check_valid_scopes?
845
- return false unless scopes
844
+ def check_valid_scopes?(scp = scopes)
845
+ return false unless scp
846
846
 
847
- (scopes - oauth_application[oauth_applications_scopes_column].split(oauth_scope_separator)).empty?
847
+ (scp - oauth_application[oauth_applications_scopes_column].split(oauth_scope_separator)).empty?
848
848
  end
849
849
 
850
850
  def check_valid_uri?(uri)
@@ -193,9 +193,8 @@ module Rodauth
193
193
 
194
194
  # do not clean up device code just yet
195
195
  update_params.delete(oauth_grants_code_column)
196
-
197
196
  update_params[oauth_grants_user_code_column] = nil
198
- update_params[oauth_grants_account_id_column] = account_id
197
+ update_params.merge!(resource_params)
199
198
 
200
199
  super(grant_params, update_params)
201
200
  end