rodauth-oauth 1.3.2 → 1.4.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -10
  3. data/doc/release_notes/1_4_0.md +49 -0
  4. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +23 -23
  5. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/frontchannel_logout.html.erb +10 -0
  6. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +1 -1
  7. data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +20 -0
  8. data/lib/generators/rodauth/oauth/views_generator.rb +2 -2
  9. data/lib/rodauth/features/oauth_application_management.rb +1 -1
  10. data/lib/rodauth/features/oauth_assertion_base.rb +1 -1
  11. data/lib/rodauth/features/oauth_authorize_base.rb +1 -1
  12. data/lib/rodauth/features/oauth_base.rb +31 -30
  13. data/lib/rodauth/features/oauth_device_code_grant.rb +2 -2
  14. data/lib/rodauth/features/oauth_dynamic_client_registration.rb +1 -0
  15. data/lib/rodauth/features/oauth_grant_management.rb +1 -1
  16. data/lib/rodauth/features/oauth_jwt.rb +3 -3
  17. data/lib/rodauth/features/oauth_jwt_base.rb +15 -10
  18. data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +1 -1
  19. data/lib/rodauth/features/oauth_jwt_jwks.rb +1 -1
  20. data/lib/rodauth/features/oauth_resource_server.rb +1 -1
  21. data/lib/rodauth/features/oauth_saml_bearer_grant.rb +79 -47
  22. data/lib/rodauth/features/oauth_tls_client_auth.rb +1 -1
  23. data/lib/rodauth/features/oauth_token_introspection.rb +1 -1
  24. data/lib/rodauth/features/oauth_token_revocation.rb +1 -1
  25. data/lib/rodauth/features/oidc.rb +27 -8
  26. data/lib/rodauth/features/oidc_backchannel_logout.rb +120 -0
  27. data/lib/rodauth/features/oidc_dynamic_client_registration.rb +25 -0
  28. data/lib/rodauth/features/oidc_frontchannel_logout.rb +134 -0
  29. data/lib/rodauth/features/oidc_logout_base.rb +76 -0
  30. data/lib/rodauth/features/oidc_rp_initiated_logout.rb +29 -6
  31. data/lib/rodauth/features/oidc_session_management.rb +89 -0
  32. data/lib/rodauth/oauth/http_extensions.rb +1 -1
  33. data/lib/rodauth/oauth/version.rb +1 -1
  34. data/locales/en.yml +9 -0
  35. data/locales/pt.yml +9 -0
  36. data/templates/check_session.str +67 -0
  37. data/templates/frontchannel_logout.str +17 -0
  38. metadata +11 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf721a3632883e34bff44e33ca84c9a5aa2248748ccecf52c180edd25086abad
4
- data.tar.gz: 59966edc773dbee470be46770532ee9159dfe370d346eca936a2ebc48a8fd224
3
+ metadata.gz: 87a675b4d44ba1003c451dc25b22c0b9fec5f346e2d03ef114d19f77ffd768da
4
+ data.tar.gz: 6921bd4b1bef1c88124bc323e293d9218959f94c985b9ed4c7e4b4452d474d55
5
5
  SHA512:
6
- metadata.gz: 6855059fe2d8225e4f3650dca01ad4ff3d82ad4e42623b43e5ea1d42ef3dbec317bf9488a2b1598556c135f9b9bf1d45ac41512d7347e3c5c55b7c5e8bd63305
7
- data.tar.gz: 9a564b8ad6812841f4a2737ee263af188e5231c49b8b475f0c575a5b0496aa6f534709552845caaaacbb73dbe1afabb9079a17265683be5df73f62075a78896e
6
+ metadata.gz: 4969a6eba906a0b180c325528c780c122b841ce7cc59c844d510a0dee8791de41cebb294f0f11d6bd23e09644732bf4b32b72e9b8ac918f0c53249fdcf4e6fd0
7
+ data.tar.gz: 4fae6adcc4b8ac567160182c52dc1c8d8f6fe34d0875f617b5284777fb64f0f5e7c4430ec9e769d78a8774e04d1c755bbabc4be6757d916b07fa5d14dc2b7eb4
data/README.md CHANGED
@@ -18,8 +18,12 @@ This is an extension to the `rodauth` gem which implements the [OAuth 2.0 framew
18
18
  * Dynamic OP
19
19
  * Form Post OP
20
20
  * 3rd Party-Init OP
21
+ * Session Management OP
22
+ * RP-Initiated Logout OP
23
+ * Front-Channel Logout OP
24
+ * Back-Channel Logout OP
21
25
 
22
- (it also passes the conformance tests for the RP-Initiated Logout OP).
26
+ The certifications were obtained using the [example OIDC server](/examples/oidc/authentication_server.rb) deployed [here](https://rodauth-oauth-oidc.onrender.com/).
23
27
 
24
28
  ## Features
25
29
 
@@ -43,7 +47,6 @@ This gem implements the following RFCs and features of OAuth:
43
47
  * `oauth_jwt_secured_authorization_response_mode` - [JWT Secured Authorization Response_mode](https://openid.net/specs/openid-financial-api-jarm.html);
44
48
  * `oauth_resource_indicators` - [Resource Indicators](https://datatracker.ietf.org/doc/html/rfc8707);
45
49
  * Access Type (Token refresh online and offline);
46
- * `oauth_http_mac` - [MAC Authentication Scheme](https://tools.ietf.org/html/draft-hammer-oauth-v2-mac-token-02);
47
50
  * `oauth_assertion_base` - [Assertion Framework](https://datatracker.ietf.org/doc/html/rfc7521);
48
51
  * `oauth_saml_bearer_grant` - [SAML 2.0 Bearer Assertion](https://datatracker.ietf.org/doc/html/rfc7522);
49
52
  * `oauth_jwt_bearer_grant` - [JWT Bearer Assertion](https://datatracker.ietf.org/doc/html/rfc7523);
@@ -52,13 +55,17 @@ This gem implements the following RFCs and features of OAuth:
52
55
  * OAuth application and token management dashboards;
53
56
  * The recommendations for [Native Apps](https://www.rfc-editor.org/rfc/rfc8252);
54
57
 
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:
58
+ It also implements several components of [OpenID Connect](https://openid.net/connect/) on top of the OAuth features it provides, including:
56
59
 
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);
60
+ * `oidc` - [OpenID Connect Core](https://gitlab.com/os85/rodauth-oauth/-/wikis/Id-Token-Authentication);
61
+ * `oidc_self_issued` - [Self-Issued OpenID Provider](https://openid.net/specs/openid-connect-core-1_0.html#SelfIssued)
62
+ * [OpenID Multiple Response Types](https://gitlab.com/os85/rodauth-oauth/-/wikis/Hybrid-flow);
63
+ * [OpenID Connect Discovery](https://gitlab.com/os85/rodauth-oauth/-/wikis/OIDC-Dynamic-Client-Registration);
64
+ * `oidc_dynamic_client_registration` - [OpenID Connect Dynamic Client Registration](https://gitlab.com/os85/rodauth-oauth/-/wikis/OIDC-Dynamic-Client-Registration);
65
+ * `oidc_session_management` - [Session Management](https://gitlab.com/os85/rodauth-oauth/-/wikis/Session-Management);
66
+ * `oidc_rp_initiated_logout` - [RP Initiated Logout](https://gitlab.com/os85/rodauth-oauth/-/wikis/RP-Initiated-Logout);
67
+ * `oidc_frontchannel_logout` - [Frontchannel Logout](https://gitlab.com/os85/rodauth-oauth/-/wikis/Frontchannel-Logout);
68
+ * `oidc_backchannel_logout` - [Backchannel Logout](https://gitlab.com/os85/rodauth-oauth/-/wikis/Backchannel-Logout);
62
69
 
63
70
  This gem supports also rails (through [rodauth-rails]((https://github.com/janko/rodauth-rails))).
64
71
 
@@ -83,8 +90,8 @@ Or install it yourself as:
83
90
  ## Resources
84
91
  | | |
85
92
  | ------------- | ----------------------------------------------------------- |
86
- | Website | https://os85.gitlab.io/rodauth-oauth/ |
87
- | Documentation | https://os85.gitlab.io/rodauth-oauth/rdoc/ |
93
+ | Website | https://honeyryderchuck.gitlab.io/rodauth-oauth/ |
94
+ | Documentation | https://honeyryderchuck.gitlab.io/rodauth-oauth/rdoc/ |
88
95
  | Wiki | https://gitlab.com/os85/rodauth-oauth/wikis/home |
89
96
  | CI | https://gitlab.com/os85/rodauth-oauth/pipelines |
90
97
 
@@ -0,0 +1,49 @@
1
+ ## 1.4.0 (08/11/2023)
2
+
3
+ ## Highlights
4
+
5
+ rodauth-oauth is now [OpenID certified](https://openid.net/certification/) for the following logout profiles:
6
+
7
+ * Session Management OP
8
+ * RP-Initiated Logout OP
9
+ * Front-Channel Logout OP
10
+ * Back-Channel Logout OP
11
+
12
+ The OIDC server used to run the test can be found [here](https://gitlab.com/os85/rodauth-oauth/-/blob/master/examples/oidc/authentication_server.rb) and deployed [here](https://rodauth-oauth-oidc.onrender.com).
13
+
14
+ ## Features
15
+
16
+ ### OIDC logout features
17
+
18
+ `rodauth-oauth` ships with the following new features:
19
+
20
+ * `oidc_sesssion_management` - enables [OIDC session management](https://openid.net/specs/openid-connect-session-1_0.html)
21
+ * `oidc_frontchannel_logout` - enables [OIDC frontchannel logout](https://openid.net/specs/openid-connect-frontchannel-1_0.html)
22
+ * `oidc_backchannel_logout` - enables [OIDC backchannel logout](https://openid.net/specs/openid-connect-backchannel-1_0.html)
23
+
24
+ which, along with the existing `oidc_rp_initiated_logout`, implemment all OIDC logout profiles.
25
+
26
+ ## Breaking changes
27
+
28
+ If you're using `oidc`, the dependency on `account_expiration` has been replaced by the `active_sessions` rodauth feature. This change is required because it fixes bugs associated with accounts expiring in order for id token invalidation to work.
29
+
30
+ If you're migrating, it's recommended that you keep depending on `account_expiration` during the transition, add `active_sessions` tables as per [rodauth specs](https://github.com/jeremyevans/rodauth/blob/master/spec/migrate/001_tables.rb#L150), and run them alongside one another for the max period ID tokens should be valid, after which you can remove `account_expiration` and its tables.
31
+
32
+ ## Improvements
33
+
34
+ ### OAuth SAML Bearer Grant per oauth application settings
35
+
36
+ The `oauth_saml_bearer_grant` feature requires a new table/resource, SAML settings, which enable "per client applicatioon" SAML settings, and therefore, make this feature usable in enterprise/multi-tenancy scenarios.
37
+
38
+ ## Bugfixes
39
+
40
+ * remove `html_safe` usage in rails views to prevent XSS in the authorize form.
41
+ * fixed for OIDC RFC 5.4 when requesting claims using scope values
42
+ * `oauth_rp_initiated_logout` does not crash anymore on logout requests with `id_token_hint`
43
+ * `oauth_rp_initiated_logout` now works with response types other than `code`
44
+ * `oauth_rp_initiated_logout` emits an ID token hint invalid message when not able to decode the `id_token_hint`
45
+
46
+ ## Chore
47
+
48
+ * Using `auth_methods` everywhere where `auth_value_methods` was used and didn't make sense.
49
+ * `oauth_tls_client_auth` is not dependent on the `oauth_jwt` feature, and can therefore be used with non-JWT access tokens, at least with the features which do not require it.
@@ -4,7 +4,7 @@
4
4
  <% end %>
5
5
  <% application_uri = rodauth.oauth_application[rodauth.oauth_applications_homepage_url_column] %>
6
6
  <% application_name = application_uri ? link_to(rodauth.oauth_application[rodauth.oauth_applications_name_column], application_uri) : rodauth.oauth_application[rodauth.oauth_applications_name_column] %>
7
- <p class="lead"><%= rodauth.authorize_page_lead(name: application_name).html_safe %></p>
7
+ <p class="lead"><%= rodauth.authorize_page_lead(name: application_name) %></p>
8
8
 
9
9
  <div class="list-group">
10
10
  <% if rodauth.oauth_application[rodauth.oauth_applications_tos_uri_column] %>
@@ -37,10 +37,10 @@
37
37
  </div>
38
38
  <% end %>
39
39
  <% end %>
40
- <%= hidden_field_tag :client_id, rodauth.raw_param("client_id") %>
40
+ <%= hidden_field_tag :client_id, rodauth.param_or_nil("client_id") %>
41
41
  <% %w[access_type response_type response_mode state redirect_uri].each do |oauth_param| %>
42
- <% if rodauth.raw_param(oauth_param) %>
43
- <%= hidden_field_tag oauth_param, rodauth.raw_param(oauth_param) %>
42
+ <% if rodauth.param_or_nil(oauth_param) %>
43
+ <%= hidden_field_tag oauth_param, rodauth.param_or_nil(oauth_param) %>
44
44
  <% end %>
45
45
  <% end %>
46
46
  <% if rodauth.features.include?(:oauth_resource_indicators) && rodauth.resource_indicators %>
@@ -49,39 +49,39 @@
49
49
  <% end %>
50
50
  <% end %>
51
51
  <% if rodauth.features.include?(:oauth_pkce) %>
52
- <% if rodauth.raw_param("code_challenge") %>
53
- <%= hidden_field_tag :code_challenge, rodauth.raw_param("code_challenge") %>
52
+ <% if rodauth.param_or_nil("code_challenge") %>
53
+ <%= hidden_field_tag :code_challenge, rodauth.param_or_nil("code_challenge") %>
54
54
  <% end %>
55
- <% if rodauth.raw_param("code_challenge_method") %>
56
- <%= hidden_field_tag :code_challenge_method, rodauth.raw_param("code_challenge_method") %>
55
+ <% if rodauth.param_or_nil("code_challenge_method") %>
56
+ <%= hidden_field_tag :code_challenge_method, rodauth.param_or_nil("code_challenge_method") %>
57
57
  <% end %>
58
58
  <% end %>
59
59
  <% if rodauth.features.include?(:oidc) %>
60
- <% if rodauth.raw_param("prompt") %>
61
- <%= hidden_field_tag :prompt, rodauth.raw_param("prompt") %>
60
+ <% if rodauth.param_or_nil("prompt") %>
61
+ <%= hidden_field_tag :prompt, rodauth.param_or_nil("prompt") %>
62
62
  <% end %>
63
- <% if rodauth.raw_param("nonce") %>
64
- <%= hidden_field_tag :nonce, rodauth.raw_param("nonce") %>
63
+ <% if rodauth.param_or_nil("nonce") %>
64
+ <%= hidden_field_tag :nonce, rodauth.param_or_nil("nonce") %>
65
65
  <% end %>
66
- <% if rodauth.raw_param("ui_locales") %>
67
- <%= hidden_field_tag :ui_locales, rodauth.raw_param("ui_locales") %>
66
+ <% if rodauth.param_or_nil("ui_locales") %>
67
+ <%= hidden_field_tag :ui_locales, rodauth.param_or_nil("ui_locales") %>
68
68
  <% end %>
69
- <% if rodauth.raw_param("claims_locales") %>
70
- <%= hidden_field_tag :claims_locales, rodauth.raw_param("claims_locales") %>
69
+ <% if rodauth.param_or_nil("claims_locales") %>
70
+ <%= hidden_field_tag :claims_locales, rodauth.param_or_nil("claims_locales") %>
71
71
  <% end %>
72
- <% if rodauth.raw_param("claims") %>
73
- <%= hidden_field_tag :claims, sanitize(rodauth.raw_param("claims")) %>
72
+ <% if rodauth.param_or_nil("claims") %>
73
+ <%= hidden_field_tag :claims, sanitize(rodauth.param_or_nil("claims")) %>
74
74
  <% end %>
75
- <% if rodauth.raw_param("acr_values") %>
76
- <%= hidden_field_tag :acr_values, rodauth.raw_param("acr_values") %>
75
+ <% if rodauth.param_or_nil("acr_values") %>
76
+ <%= hidden_field_tag :acr_values, rodauth.param_or_nil("acr_values") %>
77
77
  <% end %>
78
- <% if rodauth.raw_param("registration") %>
79
- <%= hidden_field_tag :registration, rodauth.raw_param("registration") %>
78
+ <% if rodauth.param_or_nil("registration") %>
79
+ <%= hidden_field_tag :registration, rodauth.param_or_nil("registration") %>
80
80
  <% end %>
81
81
  <% end %>
82
82
  </div>
83
83
  <p class="text-center">
84
84
  <%= submit_tag rodauth.oauth_authorize_button, class: "btn btn-outline-primary" %>
85
- <%= link_to rodauth.oauth_cancel_button, "#{rodauth.redirect_uri}?error=access_denied&error_description=The+resource+owner+or+authorization+server+denied+the+request#{"&state=\#{CGI.escape(rodauth.state)}" if rodauth.raw_param("state") }", class: "btn btn-outline-danger" %>
85
+ <%= link_to rodauth.oauth_cancel_button, "#{rodauth.redirect_uri}?error=access_denied&error_description=The+resource+owner+or+authorization+server+denied+the+request#{"&state=\#{CGI.escape(rodauth.state)}" if rodauth.param_or_nil("state") }", class: "btn btn-outline-danger" %>
86
86
  </p>
87
87
  <% end %>
@@ -0,0 +1,10 @@
1
+ <div class="mb-3">
2
+ <h1><%= rodauth.oauth_frontchannel_logout_redirecting_lead %></h1>
3
+ <p>
4
+ <%= rodauth.oauth_frontchannel_logout_redirecting_label(link: link_to(rodauth.oauth_frontchannel_logout_redirecting_link_label, rodauth.logout_redirect)) %>
5
+ </p>
6
+ <% rodauth.frontchannel_logout_urls.each do |logout_url| %>
7
+ <iframe src="<%= logout_url %>"></iframe>
8
+ <% end %>
9
+ </div>
10
+ <meta http-equiv="refresh" content="5; URL=<%= rodauth.logout_redirect %>" />
@@ -26,5 +26,5 @@
26
26
  <% end %>
27
27
  </tbody>
28
28
  </table>
29
- <%= rodauth.oauth_management_pagination_links(oauth_applications_ds).html_safe %>
29
+ <%= rodauth.oauth_management_pagination_links(oauth_applications_ds) %>
30
30
  <% end %>
@@ -59,6 +59,14 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
59
59
 
60
60
  # :oidc_rp_initiated_logout enabled
61
61
  t.string :post_logout_redirect_uris, null: false
62
+
63
+ # frontchannel logout
64
+ t.string :frontchannel_logout_uri
65
+ t.boolean :frontchannel_logout_session_required, default: false
66
+
67
+ # backchannel logout
68
+ t.string :backchannel_logout_uri
69
+ t.boolean :backchannel_logout_session_required, default: false
62
70
  end
63
71
 
64
72
  create_table :oauth_grants do |t|
@@ -107,5 +115,17 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
107
115
  t.datetime :expires_in, null: false
108
116
  t.index %i[oauth_application_id code], unique: true
109
117
  end
118
+
119
+ create_table :oauth_saml_settings do |t|
120
+ t.bigint :oauth_application_id
121
+ t.foreign_key :oauth_applications, column: :oauth_application_id
122
+ t.text :idp_cert, null: true
123
+ t.text :idp_cert_fingerprint, null: true
124
+ t.string :idp_cert_fingerprint_algorithm, null: true
125
+ t.boolean :check_idp_cert_expiration, null: true
126
+ t.text :name_identifier_format, null: true
127
+ t.string :audience, null: true
128
+ t.string :issuer, null: false, unique: true
129
+ end
110
130
  end
111
131
  end
@@ -20,11 +20,11 @@ module Rodauth::OAuth
20
20
  }.freeze
21
21
 
22
22
  class_option :features, type: :array,
23
- desc: "Roda OAuth features to generate views for (oauth_applications etc.)",
23
+ desc: "Rodauth OAuth features to generate views for (oauth_applications etc.)",
24
24
  default: DEFAULT
25
25
 
26
26
  class_option :all, aliases: "-a", type: :boolean,
27
- desc: "Generates views for all Roda OAuth features",
27
+ desc: "Generates views for all Rodauth OAuth features",
28
28
  default: false
29
29
 
30
30
  class_option :directory, aliases: "-d", type: :string,
@@ -57,7 +57,7 @@ module Rodauth
57
57
  translatable_method :oauth_no_applications_text, "No oauth applications yet!"
58
58
  translatable_method :oauth_no_grants_text, "No oauth grants yet!"
59
59
 
60
- auth_value_methods(
60
+ auth_methods(
61
61
  :oauth_application_path
62
62
  )
63
63
 
@@ -6,7 +6,7 @@ module Rodauth
6
6
  Feature.define(:oauth_assertion_base, :OauthAssertionBase) do
7
7
  depends :oauth_base
8
8
 
9
- auth_value_methods(
9
+ auth_methods(
10
10
  :assertion_grant_type?,
11
11
  :client_assertion_type?,
12
12
  :assertion_grant_type,
@@ -28,7 +28,7 @@ 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(
31
+ auth_methods(
32
32
  :resource_owner_params,
33
33
  :oauth_grants_resource_owner_columns
34
34
  )
@@ -109,13 +109,16 @@ module Rodauth
109
109
  auth_value_method :oauth_metadata_op_tos_uri, nil
110
110
 
111
111
  auth_value_methods(
112
+ :authorization_server_url,
113
+ :oauth_grants_unique_columns
114
+ )
115
+
116
+ auth_methods(
112
117
  :fetch_access_token,
113
118
  :secret_hash,
114
119
  :generate_token_hash,
115
120
  :secret_matches?,
116
- :authorization_server_url,
117
121
  :oauth_unique_id_generator,
118
- :oauth_grants_unique_columns,
119
122
  :require_authorizable_account,
120
123
  :oauth_account_ds,
121
124
  :oauth_application_ds
@@ -753,7 +756,7 @@ module Rodauth
753
756
  }
754
757
  end
755
758
 
756
- def redirect_response_error(error_code, redirect_url = redirect_uri || request.referer || default_redirect)
759
+ def redirect_response_error(error_code, message = nil)
757
760
  if accepts_json?
758
761
  status_code = if respond_to?(:"oauth_#{error_code}_response_status")
759
762
  send(:"oauth_#{error_code}_response_status")
@@ -761,37 +764,41 @@ module Rodauth
761
764
  oauth_invalid_response_status
762
765
  end
763
766
 
764
- throw_json_response_error(status_code, error_code)
767
+ throw_json_response_error(status_code, error_code, message)
765
768
  else
769
+ redirect_url = redirect_uri || request.referer || default_redirect
766
770
  redirect_url = URI.parse(redirect_url)
767
- params = []
768
-
769
- params << if respond_to?(:"oauth_#{error_code}_error_code")
770
- ["error", send(:"oauth_#{error_code}_error_code")]
771
- else
772
- ["error", error_code]
773
- end
774
-
775
- if respond_to?(:"oauth_#{error_code}_message")
776
- message = send(:"oauth_#{error_code}_message")
777
- params << ["error_description", CGI.escape(message)]
778
- end
779
-
771
+ params = response_error_params(error_code, message)
780
772
  state = param_or_nil("state")
781
-
782
- params << ["state", state] if state
783
-
773
+ params["state"] = state if state
784
774
  _redirect_response_error(redirect_url, params)
785
775
  end
786
776
  end
787
777
 
788
778
  def _redirect_response_error(redirect_url, params)
789
- params = params.map { |k, v| "#{k}=#{v}" }
790
- params << redirect_url.query if redirect_url.query
791
- redirect_url.query = params.join("&")
779
+ params = URI.encode_www_form(params)
780
+ if redirect_url.query
781
+ params << "&" unless params.empty?
782
+ params << redirect_url.query
783
+ end
784
+ redirect_url.query = params
792
785
  redirect(redirect_url.to_s)
793
786
  end
794
787
 
788
+ def response_error_params(error_code, message = nil)
789
+ code = if respond_to?(:"oauth_#{error_code}_error_code")
790
+ send(:"oauth_#{error_code}_error_code")
791
+ else
792
+ error_code
793
+ end
794
+ payload = { "error" => code }
795
+ error_description = message
796
+ error_description ||= send(:"oauth_#{error_code}_message") if respond_to?(:"oauth_#{error_code}_message")
797
+ payload["error_description"] = error_description if error_description
798
+
799
+ payload
800
+ end
801
+
795
802
  def json_response_success(body, cache = false)
796
803
  response.status = 200
797
804
  response["Content-Type"] ||= json_response_content_type
@@ -809,13 +816,7 @@ module Rodauth
809
816
 
810
817
  def throw_json_response_error(status, error_code, message = nil)
811
818
  set_response_error_status(status)
812
- code = if respond_to?(:"oauth_#{error_code}_error_code")
813
- send(:"oauth_#{error_code}_error_code")
814
- else
815
- error_code
816
- end
817
- payload = { "error" => code }
818
- payload["error_description"] = message || (send(:"oauth_#{error_code}_message") if respond_to?(:"oauth_#{error_code}_message"))
819
+ payload = response_error_params(error_code, message)
819
820
  json_payload = _json_response_body(payload)
820
821
  response["Content-Type"] ||= json_response_content_type
821
822
  response["WWW-Authenticate"] = oauth_token_type.upcase if status == 401
@@ -35,7 +35,7 @@ module Rodauth
35
35
  end
36
36
  translatable_method :oauth_grant_user_code_label, "User code"
37
37
 
38
- auth_value_methods(
38
+ auth_methods(
39
39
  :generate_user_code
40
40
  )
41
41
 
@@ -173,7 +173,7 @@ module Rodauth
173
173
  oauth_grants_user_code_column => param("user_code")
174
174
  ).update(oauth_grants_user_code_column => nil, oauth_grants_type_column => "device_code")
175
175
 
176
- return unless rs.positive?
176
+ rs if rs.positive?
177
177
  else
178
178
  super
179
179
  end
@@ -379,6 +379,7 @@ module Rodauth
379
379
 
380
380
  def convert_to_boolean(key, value)
381
381
  case value
382
+ when true, false then value
382
383
  when "true" then true
383
384
  when "false" then false
384
385
  else
@@ -21,7 +21,7 @@ module Rodauth
21
21
  auth_value_method :oauth_grants_id_pattern, Integer
22
22
  auth_value_method :oauth_grants_per_page, 20
23
23
 
24
- auth_value_methods(
24
+ auth_methods(
25
25
  :oauth_grant_path
26
26
  )
27
27
 
@@ -9,7 +9,7 @@ module Rodauth
9
9
 
10
10
  auth_value_method :oauth_jwt_access_tokens, true
11
11
 
12
- auth_value_methods(:jwt_claims)
12
+ auth_methods(:jwt_claims)
13
13
 
14
14
  def require_oauth_authorization(*scopes)
15
15
  return super unless oauth_jwt_access_tokens
@@ -99,7 +99,7 @@ module Rodauth
99
99
  end
100
100
 
101
101
  def _generate_access_token(*)
102
- return super unless oauth_jwt_access_tokens
102
+ super unless oauth_jwt_access_tokens
103
103
  end
104
104
 
105
105
  def jwt_claims(oauth_grant)
@@ -117,7 +117,7 @@ module Rodauth
117
117
  # owner is involved, such as the client credentials grant, the value
118
118
  # of "sub" SHOULD correspond to an identifier the authorization
119
119
  # server uses to indicate the client application.
120
- sub: jwt_subject(oauth_grant),
120
+ sub: jwt_subject(oauth_grant[oauth_grants_account_id_column]),
121
121
  client_id: oauth_application[oauth_applications_client_id_column],
122
122
 
123
123
  exp: issued_at + oauth_access_token_expires_in,
@@ -18,7 +18,7 @@ module Rodauth
18
18
 
19
19
  auth_value_method :oauth_jwt_jwe_copyright, nil
20
20
 
21
- auth_value_methods(
21
+ auth_methods(
22
22
  :jwt_encode,
23
23
  :jwt_decode,
24
24
  :jwt_decode_no_key,
@@ -63,12 +63,8 @@ module Rodauth
63
63
  end
64
64
  end
65
65
 
66
- def jwt_subject(oauth_grant, client_application = oauth_application)
67
- account_id = oauth_grant[oauth_grants_account_id_column]
68
-
69
- return account_id.to_s if account_id
70
-
71
- client_application[oauth_applications_client_id_column]
66
+ def jwt_subject(account_unique_id, client_application = oauth_application)
67
+ (account_unique_id || client_application[oauth_applications_client_id_column]).to_s
72
68
  end
73
69
 
74
70
  def resource_owner_params_from_jwt_claims(claims)
@@ -173,6 +169,7 @@ module Rodauth
173
169
 
174
170
  def jwt_encode(payload,
175
171
  jwks: nil,
172
+ headers: {},
176
173
  encryption_algorithm: oauth_jwt_jwe_keys.keys.dig(0, 0),
177
174
  encryption_method: oauth_jwt_jwe_keys.keys.dig(0, 1),
178
175
  jwe_key: oauth_jwt_jwe_keys[[encryption_algorithm,
@@ -186,8 +183,16 @@ module Rodauth
186
183
 
187
184
  jwk = JSON::JWK.new(key || "")
188
185
 
186
+ # update headers
187
+ headers.each_key do |k|
188
+ if jwt.respond_to?(:"#{k}=")
189
+ jwt.send(:"#{k}=", headers[k])
190
+ headers.delete(k)
191
+ end
192
+ end
193
+ jwt.header.merge(headers) unless headers.empty?
194
+
189
195
  jwt = jwt.sign(jwk, signing_algorithm)
190
- jwt.kid = jwk.thumbprint
191
196
 
192
197
  return jwt.to_s unless encryption_algorithm && encryption_method
193
198
 
@@ -329,8 +334,8 @@ module Rodauth
329
334
  end
330
335
 
331
336
  def jwt_encode(payload,
332
- signing_algorithm: oauth_jwt_keys.keys.first, **)
333
- headers = {}
337
+ signing_algorithm: oauth_jwt_keys.keys.first,
338
+ headers: {}, **)
334
339
 
335
340
  key = oauth_jwt_keys[signing_algorithm] || _jwt_key
336
341
  key = key.first if key.is_a?(Array)
@@ -8,7 +8,7 @@ module Rodauth
8
8
 
9
9
  auth_value_method :max_param_bytesize, nil if Rodauth::VERSION >= "2.26.0"
10
10
 
11
- auth_value_methods(
11
+ auth_methods(
12
12
  :require_oauth_application_from_jwt_bearer_assertion_issuer,
13
13
  :require_oauth_application_from_jwt_bearer_assertion_subject,
14
14
  :account_from_jwt_bearer_assertion
@@ -7,7 +7,7 @@ module Rodauth
7
7
  Feature.define(:oauth_jwt_jwks, :OauthJwtJwks) do
8
8
  depends :oauth_jwt_base
9
9
 
10
- auth_value_methods(:jwks_set)
10
+ auth_methods(:jwks_set)
11
11
 
12
12
  auth_server_route(:jwks) do |r|
13
13
  before_jwks_route
@@ -8,7 +8,7 @@ module Rodauth
8
8
 
9
9
  auth_value_method :is_authorization_server?, false
10
10
 
11
- auth_value_methods(
11
+ auth_methods(
12
12
  :before_introspection_request
13
13
  )
14
14