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.
- checksums.yaml +4 -4
- data/README.md +19 -12
- data/doc/release_notes/1_3_2.md +14 -0
- data/doc/release_notes/1_4_0.md +49 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +23 -23
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/frontchannel_logout.html.erb +10 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +1 -1
- data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +21 -0
- data/lib/generators/rodauth/oauth/views_generator.rb +2 -2
- data/lib/rodauth/features/oauth_application_management.rb +1 -1
- data/lib/rodauth/features/oauth_assertion_base.rb +1 -1
- data/lib/rodauth/features/oauth_authorize_base.rb +1 -1
- data/lib/rodauth/features/oauth_base.rb +31 -30
- data/lib/rodauth/features/oauth_device_code_grant.rb +2 -2
- data/lib/rodauth/features/oauth_dynamic_client_registration.rb +9 -0
- data/lib/rodauth/features/oauth_grant_management.rb +1 -1
- data/lib/rodauth/features/oauth_jwt.rb +3 -3
- data/lib/rodauth/features/oauth_jwt_base.rb +23 -12
- data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +1 -1
- data/lib/rodauth/features/oauth_jwt_jwks.rb +1 -1
- data/lib/rodauth/features/oauth_jwt_secured_authorization_request.rb +25 -6
- data/lib/rodauth/features/oauth_pushed_authorization_request.rb +33 -24
- data/lib/rodauth/features/oauth_resource_server.rb +1 -1
- data/lib/rodauth/features/oauth_saml_bearer_grant.rb +79 -47
- data/lib/rodauth/features/oauth_tls_client_auth.rb +1 -1
- data/lib/rodauth/features/oauth_token_introspection.rb +1 -1
- data/lib/rodauth/features/oauth_token_revocation.rb +1 -1
- data/lib/rodauth/features/oidc.rb +27 -8
- data/lib/rodauth/features/oidc_backchannel_logout.rb +120 -0
- data/lib/rodauth/features/oidc_dynamic_client_registration.rb +25 -0
- data/lib/rodauth/features/oidc_frontchannel_logout.rb +134 -0
- data/lib/rodauth/features/oidc_logout_base.rb +76 -0
- data/lib/rodauth/features/oidc_rp_initiated_logout.rb +29 -6
- data/lib/rodauth/features/oidc_session_management.rb +89 -0
- data/lib/rodauth/oauth/http_extensions.rb +1 -1
- data/lib/rodauth/oauth/version.rb +1 -1
- data/locales/en.yml +9 -0
- data/locales/pt.yml +9 -0
- data/templates/check_session.str +67 -0
- data/templates/frontchannel_logout.str +17 -0
- metadata +13 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 87a675b4d44ba1003c451dc25b22c0b9fec5f346e2d03ef114d19f77ffd768da
|
4
|
+
data.tar.gz: 6921bd4b1bef1c88124bc323e293d9218959f94c985b9ed4c7e4b4452d474d55
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
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
|
59
|
-
* [OpenID Multiple Response Types](https://gitlab.com/os85/rodauth-oauth/-/wikis/Hybrid-flow);
|
60
|
-
* [OpenID Connect
|
61
|
-
* [
|
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://
|
87
|
-
| Documentation | https://
|
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/
|
94
|
-
* [How to use rodauth-oauth with rails and without rodauth](https://honeyryderchuck.gitlab.io/
|
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)
|
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.
|
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.
|
43
|
-
<%= hidden_field_tag oauth_param, rodauth.
|
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.
|
53
|
-
<%= hidden_field_tag :code_challenge, rodauth.
|
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.
|
56
|
-
<%= hidden_field_tag :code_challenge_method, rodauth.
|
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.
|
61
|
-
<%= hidden_field_tag :prompt, rodauth.
|
60
|
+
<% if rodauth.param_or_nil("prompt") %>
|
61
|
+
<%= hidden_field_tag :prompt, rodauth.param_or_nil("prompt") %>
|
62
62
|
<% end %>
|
63
|
-
<% if rodauth.
|
64
|
-
<%= hidden_field_tag :nonce, rodauth.
|
63
|
+
<% if rodauth.param_or_nil("nonce") %>
|
64
|
+
<%= hidden_field_tag :nonce, rodauth.param_or_nil("nonce") %>
|
65
65
|
<% end %>
|
66
|
-
<% if rodauth.
|
67
|
-
<%= hidden_field_tag :ui_locales, rodauth.
|
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.
|
70
|
-
<%= hidden_field_tag :claims_locales, rodauth.
|
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.
|
73
|
-
<%= hidden_field_tag :claims, sanitize(rodauth.
|
72
|
+
<% if rodauth.param_or_nil("claims") %>
|
73
|
+
<%= hidden_field_tag :claims, sanitize(rodauth.param_or_nil("claims")) %>
|
74
74
|
<% end %>
|
75
|
-
<% if rodauth.
|
76
|
-
<%= hidden_field_tag :acr_values, rodauth.
|
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.
|
79
|
-
<%= hidden_field_tag :registration, rodauth.
|
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.
|
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 %>" />
|
@@ -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: "
|
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
|
27
|
+
desc: "Generates views for all Rodauth OAuth features",
|
28
28
|
default: false
|
29
29
|
|
30
30
|
class_option :directory, aliases: "-d", type: :string,
|
@@ -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
|
-
|
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,
|
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
|
790
|
-
|
791
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
@@ -9,7 +9,7 @@ module Rodauth
|
|
9
9
|
|
10
10
|
auth_value_method :oauth_jwt_access_tokens, true
|
11
11
|
|
12
|
-
|
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
|
-
|
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
|
-
|
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(
|
67
|
-
|
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
|
-
|
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
|
199
|
-
meth = encryption_method.to_sym
|
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
|
-
|
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
|
-
|
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
|