rodauth-oauth 1.3.2 → 1.5.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -10
  3. data/doc/release_notes/1_4_0.md +57 -0
  4. data/doc/release_notes/1_5_0.md +20 -0
  5. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +28 -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 +37 -1
  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 +49 -38
  14. data/lib/rodauth/features/oauth_device_code_grant.rb +2 -2
  15. data/lib/rodauth/features/oauth_dpop.rb +410 -0
  16. data/lib/rodauth/features/oauth_dynamic_client_registration.rb +12 -2
  17. data/lib/rodauth/features/oauth_grant_management.rb +1 -1
  18. data/lib/rodauth/features/oauth_jwt.rb +12 -13
  19. data/lib/rodauth/features/oauth_jwt_base.rb +57 -34
  20. data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +1 -1
  21. data/lib/rodauth/features/oauth_jwt_jwks.rb +1 -1
  22. data/lib/rodauth/features/oauth_resource_indicators.rb +1 -1
  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 +2 -4
  26. data/lib/rodauth/features/oauth_token_introspection.rb +3 -3
  27. data/lib/rodauth/features/oauth_token_revocation.rb +1 -1
  28. data/lib/rodauth/features/oidc.rb +32 -11
  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 +91 -0
  35. data/lib/rodauth/oauth/database_extensions.rb +4 -0
  36. data/lib/rodauth/oauth/http_extensions.rb +1 -1
  37. data/lib/rodauth/oauth/ttl_store.rb +1 -1
  38. data/lib/rodauth/oauth/version.rb +1 -1
  39. data/locales/en.yml +19 -0
  40. data/locales/pt.yml +9 -0
  41. data/templates/authorize.str +1 -0
  42. data/templates/check_session.str +67 -0
  43. data/templates/frontchannel_logout.str +17 -0
  44. metadata +14 -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: 40ff3b3b3de0595eae98f218aec2f8e876f18329061c537e91e5841ab35e67dc
4
+ data.tar.gz: 19692e86d66400a9e7227f655bdc6375e38554cb52b97ee3a5e22cceab582168
5
5
  SHA512:
6
- metadata.gz: 6855059fe2d8225e4f3650dca01ad4ff3d82ad4e42623b43e5ea1d42ef3dbec317bf9488a2b1598556c135f9b9bf1d45ac41512d7347e3c5c55b7c5e8bd63305
7
- data.tar.gz: 9a564b8ad6812841f4a2737ee263af188e5231c49b8b475f0c575a5b0496aa6f534709552845caaaacbb73dbe1afabb9079a17265683be5df73f62075a78896e
6
+ metadata.gz: cd6efdeda012c25d83949e7f0ea2aa043238851f95bf1217b8156786f7376d39792caed37a2af68fc7777ed5a892fa2f9e5185efe1f8a3e7168df07de84b954d
7
+ data.tar.gz: 0e435eea239f81ff16db08b187ce7bb06ba3d25359603906e48610e38912162b35c79c3d8b7fbfa74df2c56f52bb653d61b999da76c1cc8d445f2077b876c578
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,57 @@
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
+ Some `auth_value_methods` were changed to `auth_methods` everywhere where it made sense. If you were overriding them, you'll have to wrap them in a block:
33
+
34
+ ```ruby
35
+ # in 1.3.2
36
+ oauth_jwt_issuer "http://myissuer.com"
37
+ # in 1.4.0
38
+ oauth_jwt_issuer { "http://myissuer.com" }
39
+ ```
40
+
41
+ ## Improvements
42
+
43
+ ### OAuth SAML Bearer Grant per oauth application settings
44
+
45
+ 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.
46
+
47
+ ## Bugfixes
48
+
49
+ * remove `html_safe` usage in rails views to prevent XSS in the authorize form.
50
+ * fixed for OIDC RFC 5.4 when requesting claims using scope values
51
+ * `oauth_rp_initiated_logout` does not crash anymore on logout requests with `id_token_hint`
52
+ * `oauth_rp_initiated_logout` now works with response types other than `code`
53
+ * `oauth_rp_initiated_logout` emits an ID token hint invalid message when not able to decode the `id_token_hint`
54
+
55
+ ## Chore
56
+
57
+ * `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.
@@ -0,0 +1,20 @@
1
+ # 1.5.0
2
+
3
+ ## Highlights
4
+
5
+ ### OAuth DPoP Support
6
+
7
+ `rodauth-oauth` supports Demonstrating Proof-of-Possession at the Application Layer (also known as DPoP), via the `oauth_dpop` feature. This provides a mechanism to bind access tokens to a particular client based on public key cryptography.
8
+
9
+ More info about the feature [in the docs](https://gitlab.com/os85/rodauth-oauth/-/wikis/DPoP).
10
+
11
+ ## Improvements
12
+
13
+ All features managing cookies are now able to set configure them as "session cookies" (i.e. removed on browser shutdown) by setting the expiration interval auth method to `nil`. This ncludes:
14
+
15
+ * `oauth_prompt_login_interval` (from the `oidc` feature)
16
+ * `oauth_oidc_user_agent_state_cookie_expires_in` (from the `oidc_session_management` feature)
17
+
18
+ ## Bugfixes
19
+
20
+ * when using the `oauth_token_instrospection` feature, the `token_type` has been fixed to show "Bearer" (instead of "access_token").
@@ -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,44 @@
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
+ <% end %>
81
+ <% end %>
82
+ <% if rodauth.features.include?(:oidc) %>
83
+ <% if rodauth.param_or_nil("dpop_jkt") %>
84
+ <%= hidden_field_tag :dpop_jkt, rodauth.param_or_nil("dpop_jkt") %>
80
85
  <% end %>
81
86
  <% end %>
82
87
  </div>
83
88
  <p class="text-center">
84
89
  <%= 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" %>
90
+ <%= 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
91
  </p>
87
92
  <% 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 %>
@@ -49,6 +49,9 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
49
49
  t.boolean :require_signed_request_object, null: true
50
50
  t.boolean :require_pushed_authorization_requests, null: false, default: false
51
51
 
52
+ # :oauth_dpop
53
+ t.string :dpop_bound_access_tokens, null: true
54
+
52
55
  # :oauth_tls_client_auth
53
56
  t.string :tls_client_auth_subject_dn, null: true
54
57
  t.string :tls_client_auth_san_dns, null: true
@@ -59,6 +62,14 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
59
62
 
60
63
  # :oidc_rp_initiated_logout enabled
61
64
  t.string :post_logout_redirect_uris, null: false
65
+
66
+ # frontchannel logout
67
+ t.string :frontchannel_logout_uri
68
+ t.boolean :frontchannel_logout_session_required, default: false
69
+
70
+ # backchannel logout
71
+ t.string :backchannel_logout_uri
72
+ t.boolean :backchannel_logout_session_required, default: false
62
73
  end
63
74
 
64
75
  create_table :oauth_grants do |t|
@@ -78,6 +89,9 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
78
89
  t.datetime :created_at, null: false, default: -> { "CURRENT_TIMESTAMP(6)" }
79
90
  t.string :access_type, null: false, default: "offline"
80
91
 
92
+ # :oauth_dpop enabled
93
+ t.string :dpop_jwk, null: true
94
+
81
95
  # :oauth_pkce enabled
82
96
  t.string :code_challenge
83
97
  t.string :code_challenge_method
@@ -97,15 +111,37 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
97
111
  t.string :acr
98
112
  t.string :claims_locales
99
113
  t.string :claims
114
+
115
+ # :oauth_dpop enabled
116
+ t.string :dpop_jkt
100
117
  end
101
118
 
102
119
  create_table :oauth_pushed_requests do |t|
103
120
  t.bigint :oauth_application_id
104
121
  t.foreign_key :oauth_applications, column: :oauth_application_id
105
122
  t.string :code, null: false, index: { unique: true }
123
+ t.index %i[oauth_application_id code], unique: true
106
124
  t.string :params, null: false
107
125
  t.datetime :expires_in, null: false
108
- t.index %i[oauth_application_id code], unique: true
126
+ # :oauth_dpop
127
+ t.string :dpop_jkt
128
+ end
129
+
130
+ create_table :oauth_saml_settings do |t|
131
+ t.bigint :oauth_application_id
132
+ t.foreign_key :oauth_applications, column: :oauth_application_id
133
+ t.text :idp_cert, null: true
134
+ t.text :idp_cert_fingerprint, null: true
135
+ t.string :idp_cert_fingerprint_algorithm, null: true
136
+ t.boolean :check_idp_cert_expiration, null: true
137
+ t.text :name_identifier_format, null: true
138
+ t.string :audience, null: true
139
+ t.string :issuer, null: false, unique: true
140
+ end
141
+
142
+ create_table :oauth_dpop_proofs, primary_key: :jti do |t|
143
+ t.string :jti, null: false
144
+ t.datetime :first_use, null: false, default: -> { "CURRENT_TIMESTAMP(6)" }
109
145
  end
110
146
  end
111
147
  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
@@ -234,16 +237,22 @@ module Rodauth
234
237
  return
235
238
  end
236
239
  else
237
- value = request.env["HTTP_AUTHORIZATION"]
240
+ token = fetch_access_token_from_authorization_header
241
+ end
238
242
 
239
- return unless value && !value.empty?
243
+ return if token.nil? || token.empty?
240
244
 
241
- scheme, token = value.split(" ", 2)
245
+ token
246
+ end
242
247
 
243
- return unless scheme.downcase == oauth_token_type
244
- end
248
+ def fetch_access_token_from_authorization_header(token_type = oauth_token_type)
249
+ value = request.env["HTTP_AUTHORIZATION"]
245
250
 
246
- return if token.nil? || token.empty?
251
+ return unless value && !value.empty?
252
+
253
+ scheme, token = value.split(" ", 2)
254
+
255
+ return unless scheme.downcase == token_type
247
256
 
248
257
  token
249
258
  end
@@ -350,7 +359,7 @@ module Rodauth
350
359
  # parse client id and secret
351
360
  #
352
361
  def require_oauth_application
353
- @oauth_application = if (token = ((v = request.env["HTTP_AUTHORIZATION"]) && v[/\A *Basic (.*)\Z/, 1]))
362
+ @oauth_application = if (token = (v = request.env["HTTP_AUTHORIZATION"]) && v[/\A *Basic (.*)\Z/, 1])
354
363
  # client_secret_basic
355
364
  require_oauth_application_from_client_secret_basic(token)
356
365
  elsif (client_id = param_or_nil("client_id"))
@@ -753,7 +762,7 @@ module Rodauth
753
762
  }
754
763
  end
755
764
 
756
- def redirect_response_error(error_code, redirect_url = redirect_uri || request.referer || default_redirect)
765
+ def redirect_response_error(error_code, message = nil)
757
766
  if accepts_json?
758
767
  status_code = if respond_to?(:"oauth_#{error_code}_response_status")
759
768
  send(:"oauth_#{error_code}_response_status")
@@ -761,37 +770,41 @@ module Rodauth
761
770
  oauth_invalid_response_status
762
771
  end
763
772
 
764
- throw_json_response_error(status_code, error_code)
773
+ throw_json_response_error(status_code, error_code, message)
765
774
  else
775
+ redirect_url = redirect_uri || request.referer || default_redirect
766
776
  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
-
777
+ params = response_error_params(error_code, message)
780
778
  state = param_or_nil("state")
781
-
782
- params << ["state", state] if state
783
-
779
+ params["state"] = state if state
784
780
  _redirect_response_error(redirect_url, params)
785
781
  end
786
782
  end
787
783
 
788
784
  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("&")
785
+ params = URI.encode_www_form(params)
786
+ if redirect_url.query
787
+ params << "&" unless params.empty?
788
+ params << redirect_url.query
789
+ end
790
+ redirect_url.query = params
792
791
  redirect(redirect_url.to_s)
793
792
  end
794
793
 
794
+ def response_error_params(error_code, message = nil)
795
+ code = if respond_to?(:"oauth_#{error_code}_error_code")
796
+ send(:"oauth_#{error_code}_error_code")
797
+ else
798
+ error_code
799
+ end
800
+ payload = { "error" => code }
801
+ error_description = message
802
+ error_description ||= send(:"oauth_#{error_code}_message") if respond_to?(:"oauth_#{error_code}_message")
803
+ payload["error_description"] = error_description if error_description
804
+
805
+ payload
806
+ end
807
+
795
808
  def json_response_success(body, cache = false)
796
809
  response.status = 200
797
810
  response["Content-Type"] ||= json_response_content_type
@@ -809,19 +822,17 @@ module Rodauth
809
822
 
810
823
  def throw_json_response_error(status, error_code, message = nil)
811
824
  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"))
825
+ payload = response_error_params(error_code, message)
819
826
  json_payload = _json_response_body(payload)
820
827
  response["Content-Type"] ||= json_response_content_type
821
- response["WWW-Authenticate"] = oauth_token_type.upcase if status == 401
828
+ response["WWW-Authenticate"] = www_authenticate_header(payload) if status == 401
822
829
  return_response(json_payload)
823
830
  end
824
831
 
832
+ def www_authenticate_header(*)
833
+ oauth_token_type.capitalize
834
+ end
835
+
825
836
  def _json_response_body(hash)
826
837
  return super if features.include?(:json)
827
838
 
@@ -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