rodauth-oauth 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +19 -12
  3. data/doc/release_notes/1_3_2.md +14 -0
  4. data/doc/release_notes/1_4_0.md +49 -0
  5. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +23 -23
  6. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/frontchannel_logout.html.erb +10 -0
  7. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +1 -1
  8. data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +21 -0
  9. data/lib/generators/rodauth/oauth/views_generator.rb +2 -2
  10. data/lib/rodauth/features/oauth_application_management.rb +1 -1
  11. data/lib/rodauth/features/oauth_assertion_base.rb +1 -1
  12. data/lib/rodauth/features/oauth_authorize_base.rb +1 -1
  13. data/lib/rodauth/features/oauth_base.rb +31 -30
  14. data/lib/rodauth/features/oauth_device_code_grant.rb +2 -2
  15. data/lib/rodauth/features/oauth_dynamic_client_registration.rb +9 -0
  16. data/lib/rodauth/features/oauth_grant_management.rb +1 -1
  17. data/lib/rodauth/features/oauth_jwt.rb +3 -3
  18. data/lib/rodauth/features/oauth_jwt_base.rb +23 -12
  19. data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +1 -1
  20. data/lib/rodauth/features/oauth_jwt_jwks.rb +1 -1
  21. data/lib/rodauth/features/oauth_jwt_secured_authorization_request.rb +25 -6
  22. data/lib/rodauth/features/oauth_pushed_authorization_request.rb +33 -24
  23. data/lib/rodauth/features/oauth_resource_server.rb +1 -1
  24. data/lib/rodauth/features/oauth_saml_bearer_grant.rb +79 -47
  25. data/lib/rodauth/features/oauth_tls_client_auth.rb +1 -1
  26. data/lib/rodauth/features/oauth_token_introspection.rb +1 -1
  27. data/lib/rodauth/features/oauth_token_revocation.rb +1 -1
  28. data/lib/rodauth/features/oidc.rb +27 -8
  29. data/lib/rodauth/features/oidc_backchannel_logout.rb +120 -0
  30. data/lib/rodauth/features/oidc_dynamic_client_registration.rb +25 -0
  31. data/lib/rodauth/features/oidc_frontchannel_logout.rb +134 -0
  32. data/lib/rodauth/features/oidc_logout_base.rb +76 -0
  33. data/lib/rodauth/features/oidc_rp_initiated_logout.rb +29 -6
  34. data/lib/rodauth/features/oidc_session_management.rb +89 -0
  35. data/lib/rodauth/oauth/http_extensions.rb +1 -1
  36. data/lib/rodauth/oauth/version.rb +1 -1
  37. data/locales/en.yml +9 -0
  38. data/locales/pt.yml +9 -0
  39. data/templates/check_session.str +67 -0
  40. data/templates/frontchannel_logout.str +17 -0
  41. metadata +13 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac42aa0fb7d65030b403957a408d6d5b1f999614c957ec8176d814030ddb9381
4
- data.tar.gz: f67cf98dfdb162d1e015d3930b569756f2a25737a64e68ed36fcf31e2d672be2
3
+ metadata.gz: 87a675b4d44ba1003c451dc25b22c0b9fec5f346e2d03ef114d19f77ffd768da
4
+ data.tar.gz: 6921bd4b1bef1c88124bc323e293d9218959f94c985b9ed4c7e4b4452d474d55
5
5
  SHA512:
6
- metadata.gz: e86da1d43f30dfb18e1ae530dc5b1cc9cf69903d32a39bade2dc5881075140d79ef5644159ff40b1406b58dfb12197c79d13f683f20b431330d02d452b3cf87e
7
- data.tar.gz: ad35b6bc881f1e22f7cb7227115daee9556ebeada9e10b21e8ecfeb9dc0e7a51ddd606ef481f1125064e49d6ee39076bd8bc2043d58d0d103df6a27813a022db
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,15 +90,15 @@ 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
 
91
98
  ## Articles
92
99
 
93
- * [How to use rodauth-oauth with rails and rodauth](https://honeyryderchuck.gitlab.io/httpx/2021/03/15/oidc-provider-on-rails-using-rodauth-oauth.html)
94
- * [How to use rodauth-oauth with rails and without rodauth](https://honeyryderchuck.gitlab.io/httpx/2021/09/08/using-rodauth-oauth-in-rails-without-rodauth-based-auth.html)
100
+ * [How to use rodauth-oauth with rails and rodauth](https://honeyryderchuck.gitlab.io/2021/03/15/oidc-provider-on-rails-using-rodauth-oauth.html)
101
+ * [How to use rodauth-oauth with rails and without rodauth](https://honeyryderchuck.gitlab.io/2021/09/08/using-rodauth-oauth-in-rails-without-rodauth-based-auth.html)
95
102
 
96
103
  ## Usage
97
104
 
@@ -0,0 +1,14 @@
1
+ ### 1.3.2 (27/07/2023)
2
+
3
+ #### Improvements
4
+
5
+ * `require_signed_request_object` option for JAR (`oauth_jwt_secured_authorization_request` plugin) is now supported:
6
+ * in the oauth server metadata endpoint
7
+ * as a plugin config option (`oauth_require_signed_request_object`, defaults to `false`)
8
+ * as a oauth dynamic registration endpoint param (`require_signed_request_object`, requires corresponding columnn)
9
+ * enforces JAR-based authorization, andd does not allow unsigned JAR JWTs, when turned on.
10
+
11
+ #### Bugfixes
12
+
13
+ * JWT decoding failed in circumstances where a declared encryption algo didn't have key/method declared.
14
+ * fix for when PAR (`oauth_pushed_authorization_request` feature) is used with JAR (`oauth_jwt_secured_authorization_request` plugin), and PAR `request_uri` param wasn't being removed when validating authorize request parameters, thereby making JAR logic evaluate it as a JAR `requuest_uri` (it is now correctly not taken into account in such a case);
@@ -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 %>
@@ -46,6 +46,7 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
46
46
  t.string :request_object_encryption_alg, null: true
47
47
  t.string :request_object_encryption_enc, null: true
48
48
  t.string :request_uris, null: true
49
+ t.boolean :require_signed_request_object, null: true
49
50
  t.boolean :require_pushed_authorization_requests, null: false, default: false
50
51
 
51
52
  # :oauth_tls_client_auth
@@ -58,6 +59,14 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
58
59
 
59
60
  # :oidc_rp_initiated_logout enabled
60
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
61
70
  end
62
71
 
63
72
  create_table :oauth_grants do |t|
@@ -106,5 +115,17 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
106
115
  t.datetime :expires_in, null: false
107
116
  t.index %i[oauth_application_id code], unique: true
108
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
109
130
  end
110
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
@@ -200,6 +200,14 @@ module Rodauth
200
200
  when "client_name"
201
201
  register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(value)) unless value.is_a?(String)
202
202
  key = oauth_applications_name_column
203
+ when "require_signed_request_object"
204
+ unless respond_to?(:oauth_applications_require_signed_request_object_column)
205
+ register_throw_json_response_error("invalid_client_metadata",
206
+ register_invalid_param_message(key))
207
+ end
208
+ request_params[key] = value = convert_to_boolean(key, value)
209
+
210
+ key = oauth_applications_require_signed_request_object_column
203
211
  when "require_pushed_authorization_requests"
204
212
  unless respond_to?(:oauth_applications_require_pushed_authorization_requests_column)
205
213
  register_throw_json_response_error("invalid_client_metadata",
@@ -371,6 +379,7 @@ module Rodauth
371
379
 
372
380
  def convert_to_boolean(key, value)
373
381
  case value
382
+ when true, false then value
374
383
  when "true" then true
375
384
  when "false" then false
376
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,18 @@ 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
196
+
197
+ return jwt.to_s unless encryption_algorithm && encryption_method
191
198
 
192
199
  if jwks && (jwk = jwks.find { |k| k[:use] == "enc" && k[:alg] == encryption_algorithm && k[:enc] == encryption_method })
193
200
  jwk = JSON::JWK.new(jwk)
@@ -195,8 +202,8 @@ module Rodauth
195
202
  jwe.to_s
196
203
  elsif jwe_key
197
204
  jwe_key = jwe_key.first if jwe_key.is_a?(Array)
198
- algorithm = encryption_algorithm.to_sym if encryption_algorithm
199
- meth = encryption_method.to_sym if encryption_method
205
+ algorithm = encryption_algorithm.to_sym
206
+ meth = encryption_method.to_sym
200
207
  jwt.encrypt(jwe_key, algorithm, meth)
201
208
  else
202
209
  jwt.to_s
@@ -246,6 +253,8 @@ module Rodauth
246
253
  jws
247
254
  elsif jws_key
248
255
  JSON::JWT.decode(token, jws_key)
256
+ else
257
+ JSON::JWT.decode(token, nil, jws_algorithm)
249
258
  end
250
259
  elsif (jwks = auth_server_jwks_set)
251
260
  JSON::JWT.decode(token, JSON::JWK::Set.new(jwks))
@@ -325,8 +334,8 @@ module Rodauth
325
334
  end
326
335
 
327
336
  def jwt_encode(payload,
328
- signing_algorithm: oauth_jwt_keys.keys.first, **)
329
- headers = {}
337
+ signing_algorithm: oauth_jwt_keys.keys.first,
338
+ headers: {}, **)
330
339
 
331
340
  key = oauth_jwt_keys[signing_algorithm] || _jwt_key
332
341
  key = key.first if key.is_a?(Array)
@@ -428,6 +437,8 @@ module Rodauth
428
437
  end
429
438
  elsif jws_key
430
439
  JWT.decode(token, jws_key, true, algorithms: [jws_algorithm], **verify_claims_params).first
440
+ else
441
+ JWT.decode(token, jws_key, false, **verify_claims_params).first
431
442
  end
432
443
  elsif (jwks = auth_server_jwks_set)
433
444
  algorithms = jwks[:keys].select { |k| k[:use] == "sig" }.map { |k| k[:alg] }
@@ -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