rodauth-oauth 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -0
- data/doc/release_notes/1_1_0.md +1 -1
- data/doc/release_notes/1_3_0.md +38 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +3 -0
- data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +11 -10
- data/lib/rodauth/features/oauth_authorization_code_grant.rb +54 -32
- data/lib/rodauth/features/oauth_authorize_base.rb +8 -0
- data/lib/rodauth/features/oauth_base.rb +16 -16
- data/lib/rodauth/features/oauth_dynamic_client_registration.rb +7 -7
- data/lib/rodauth/features/oauth_implicit_grant.rb +22 -4
- data/lib/rodauth/features/oauth_jwt_secured_authorization_response_mode.rb +126 -0
- data/lib/rodauth/features/oauth_management_base.rb +1 -3
- data/lib/rodauth/features/oidc.rb +87 -50
- data/lib/rodauth/features/oidc_dynamic_client_registration.rb +39 -1
- data/lib/rodauth/features/oidc_self_issued.rb +73 -0
- data/lib/rodauth/oauth/version.rb +1 -1
- data/templates/authorize.str +1 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4d7d5f8b68686703954bf4e335cef0ea33f9e31c94c439df84f08e8ff3270829
|
4
|
+
data.tar.gz: 1da57ba2082818a74dbca4d1c6bcab0c15f97da891e12c03a8bf91440a4edcfd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8230b54e51d2081e25d1386d6294745d54eebbe11a6677bdb9cade14e0a418658bc2b8a67ae2e6355458f4b43d8a2df1700cd3e0496fa8a10e690318f3d03ba0
|
7
|
+
data.tar.gz: 31ab5721a6464b751860b6896f47999e189592582842ba419ab0a057ff38af98612d54a8b00177092e2fe5993af1e5554cecafbbfaab18a495656117f19ce4fd
|
data/README.md
CHANGED
@@ -17,6 +17,7 @@ This is an extension to the `rodauth` gem which implements the [OAuth 2.0 framew
|
|
17
17
|
* Config OP
|
18
18
|
* Dynamic OP
|
19
19
|
* Form Post OP
|
20
|
+
* 3rd Party-Init OP
|
20
21
|
|
21
22
|
(it also passes the conformance tests for the RP-Initiated Logout OP).
|
22
23
|
|
@@ -39,6 +40,7 @@ This gem implements the following RFCs and features of OAuth:
|
|
39
40
|
* `oauth_tls_client_auth` - [Mutual-TLS Client Authentication](https://datatracker.ietf.org/doc/html/rfc8705);
|
40
41
|
* `oauth_jwt` - [JWT Access Tokens](https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-07);
|
41
42
|
* `oauth_jwt_secured_authorization_request` - [JWT Secured Authorization Request](https://tools.ietf.org/html/draft-ietf-oauth-jwsreq-20);
|
43
|
+
* `oauth_jwt_secured_authorization_response_mode` - [JWT Secured Authorization Response_mode](https://openid.net/specs/openid-financial-api-jarm.html);
|
42
44
|
* `oauth_resource_indicators` - [Resource Indicators](https://datatracker.ietf.org/doc/html/rfc8707);
|
43
45
|
* Access Type (Token refresh online and offline);
|
44
46
|
* `oauth_http_mac` - [MAC Authentication Scheme](https://tools.ietf.org/html/draft-hammer-oauth-v2-mac-token-02);
|
data/doc/release_notes/1_1_0.md
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
## 1.3.0 (02/04/2023)
|
2
|
+
|
3
|
+
## Features
|
4
|
+
|
5
|
+
### Self-Signed Issued Tokens
|
6
|
+
|
7
|
+
`rodauth-oauth` supports self-signed issued tokens, via the `oidc_self_issued` feature.
|
8
|
+
|
9
|
+
More info about the feature [in the docs](https://gitlab.com/os85/rodauth-oauth/-/wikis/Self-Issued-OpenID).
|
10
|
+
|
11
|
+
#### JARM
|
12
|
+
|
13
|
+
`rodauth-oauth` supports JWT-secured Authorization Response Mode, also known as JARM, via the `oauth_jwt_secured_authorization_response_mode`.
|
14
|
+
|
15
|
+
More info about the feature [in the docs](https://gitlab.com/os85/rodauth-oauth/-/wikis/JWT-Secured-Authorization-Response-Mode).
|
16
|
+
|
17
|
+
## Improvements
|
18
|
+
|
19
|
+
### `fill_with_account_claims` auth method
|
20
|
+
|
21
|
+
`fill_with_account_claims` is now exposed as an auth method. This allows one to override to be able to cover certain requirements, such as aggregated and distributed claims. Here's a [link to the docs](https://gitlab.com/os85/rodauth-oauth/-/wikis/Id-Token-Authentication#claim-types) explaining how to do it.
|
22
|
+
|
23
|
+
### oidc: only generate refresh token when `offline_access` scope is used.
|
24
|
+
|
25
|
+
When the `oidc` feature is used, refresh tokens won't be generated anymore by default; in order to do so, the `offline_access` needs to be requested for in the respective authorization request, [as the spec mandates](https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess).
|
26
|
+
|
27
|
+
### oidc: implicit grant loaded by default
|
28
|
+
|
29
|
+
The `oidc` feature now loads the `oauth_implicit_grant` feature by default. This hadn't been done before due to the wish to ship a secure integration by default, but since then, spec compliance became more prioritary, and this is a requirement.
|
30
|
+
|
31
|
+
## Bugfixes
|
32
|
+
|
33
|
+
* rails integration: activerecord migrations fixes:
|
34
|
+
* use `bigint` for foreign keys;
|
35
|
+
* index creation instruction with the wrong syntax;
|
36
|
+
* set precision 6 for default timestamps, to comply with AR defaults;
|
37
|
+
* add missing `code` column to the `oauth_pushed_requests` table;
|
38
|
+
* oidc: when using the `id_token` , or any composite response type including `id_token`, using any response mode other than `fragment` will result in an invalid request.
|
@@ -75,6 +75,9 @@
|
|
75
75
|
<% if params[:acr_values] %>
|
76
76
|
<%= hidden_field_tag :acr_values, params[:acr_values] %>
|
77
77
|
<% end %>
|
78
|
+
<% if params[:registration] %>
|
79
|
+
<%= hidden_field_tag :registration, params[:registration] %>
|
80
|
+
<% end %>
|
78
81
|
<% end %>
|
79
82
|
</div>
|
80
83
|
<p class="text-center">
|
@@ -1,7 +1,7 @@
|
|
1
1
|
class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
|
2
2
|
def change
|
3
3
|
create_table :oauth_applications do |t|
|
4
|
-
t.
|
4
|
+
t.bigint :account_id
|
5
5
|
t.foreign_key :accounts, column: :account_id
|
6
6
|
t.string :name, null: false
|
7
7
|
t.string :description, null: true
|
@@ -11,7 +11,7 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
|
|
11
11
|
t.string :client_secret, null: false, index: { unique: true }
|
12
12
|
t.string :registration_access_token, null: true
|
13
13
|
t.string :scopes, null: false
|
14
|
-
t.datetime :created_at, null: false, default: -> { "CURRENT_TIMESTAMP" }
|
14
|
+
t.datetime :created_at, null: false, default: -> { "CURRENT_TIMESTAMP(6)" }
|
15
15
|
|
16
16
|
# :oauth_dynamic_client_configuration enabled, extra optional params
|
17
17
|
t.string :token_endpoint_auth_method, null: true
|
@@ -61,20 +61,20 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
|
|
61
61
|
end
|
62
62
|
|
63
63
|
create_table :oauth_grants do |t|
|
64
|
-
t.
|
64
|
+
t.bigint :account_id
|
65
65
|
t.foreign_key :accounts, column: :account_id
|
66
|
-
t.
|
66
|
+
t.bigint :oauth_application_id
|
67
67
|
t.foreign_key :oauth_applications, column: :oauth_application_id
|
68
68
|
t.string :type, null: true
|
69
69
|
t.string :code, null: true
|
70
70
|
t.index(%i[oauth_application_id code], unique: true)
|
71
|
-
t.string :token, unique: true
|
72
|
-
t.string :refresh_token, unique: true
|
71
|
+
t.string :token, index: { unique: true }
|
72
|
+
t.string :refresh_token, index: { unique: true }
|
73
73
|
t.datetime :expires_in, null: false
|
74
74
|
t.string :redirect_uri
|
75
75
|
t.datetime :revoked_at
|
76
76
|
t.string :scopes, null: false
|
77
|
-
t.datetime :created_at, null: false, default: -> { "CURRENT_TIMESTAMP" }
|
77
|
+
t.datetime :created_at, null: false, default: -> { "CURRENT_TIMESTAMP(6)" }
|
78
78
|
t.string :access_type, null: false, default: "offline"
|
79
79
|
|
80
80
|
# :oauth_pkce enabled
|
@@ -82,7 +82,7 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
|
|
82
82
|
t.string :code_challenge_method
|
83
83
|
|
84
84
|
# :oauth_device_code_grant enabled
|
85
|
-
t.string :user_code, null: true, unique: true
|
85
|
+
t.string :user_code, null: true, index: { unique: true }
|
86
86
|
t.datetime :last_polled_at, null: true
|
87
87
|
|
88
88
|
# :oauth_tls_client_auth
|
@@ -99,11 +99,12 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
|
|
99
99
|
end
|
100
100
|
|
101
101
|
create_table :oauth_pushed_requests do |t|
|
102
|
-
t.
|
102
|
+
t.bigint :oauth_application_id
|
103
103
|
t.foreign_key :oauth_applications, column: :oauth_application_id
|
104
|
+
t.string :code, null: false, index: { unique: true }
|
104
105
|
t.string :params, null: false
|
105
106
|
t.datetime :expires_in, null: false
|
106
107
|
t.index %i[oauth_application_id code], unique: true
|
107
108
|
end
|
108
109
|
end
|
109
|
-
end
|
110
|
+
end
|
@@ -27,7 +27,19 @@ module Rodauth
|
|
27
27
|
|
28
28
|
response_mode = param_or_nil("response_mode")
|
29
29
|
|
30
|
-
|
30
|
+
return unless response_mode
|
31
|
+
|
32
|
+
redirect_response_error("invalid_request") unless oauth_response_modes_supported.include?(response_mode)
|
33
|
+
|
34
|
+
response_type = param_or_nil("response_type")
|
35
|
+
|
36
|
+
return unless response_type.nil? || response_type == "code"
|
37
|
+
|
38
|
+
redirect_response_error("invalid_request") unless oauth_response_modes_for_code_supported.include?(response_mode)
|
39
|
+
end
|
40
|
+
|
41
|
+
def oauth_response_modes_for_code_supported
|
42
|
+
%w[query form_post]
|
31
43
|
end
|
32
44
|
|
33
45
|
def validate_token_params
|
@@ -67,55 +79,65 @@ module Rodauth
|
|
67
79
|
redirect_url = URI.parse(redirect_uri)
|
68
80
|
case mode
|
69
81
|
when "query"
|
70
|
-
params =
|
82
|
+
params = [URI.encode_www_form(params)]
|
71
83
|
params << redirect_url.query if redirect_url.query
|
72
84
|
redirect_url.query = params.join("&")
|
73
85
|
redirect(redirect_url.to_s)
|
74
86
|
when "form_post"
|
75
|
-
|
76
|
-
|
77
|
-
<
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
params.map do |name, value|
|
82
|
-
"<input type=\"hidden\" name=\"#{scope.h(name)}\" value=\"#{scope.h(value)}\" />"
|
83
|
-
end.join
|
84
|
-
}
|
85
|
-
<input type="submit" class="btn btn-outline-primary" value="#{scope.h(oauth_authorize_post_button)}"/>
|
86
|
-
</form>
|
87
|
-
</body>
|
88
|
-
</html>
|
89
|
-
FORM
|
87
|
+
inline_html = form_post_response_html(redirect_uri) do
|
88
|
+
params.map do |name, value|
|
89
|
+
"<input type=\"hidden\" name=\"#{scope.h(name)}\" value=\"#{scope.h(value)}\" />"
|
90
|
+
end.join
|
91
|
+
end
|
92
|
+
scope.view layout: false, inline: inline_html
|
90
93
|
end
|
91
94
|
end
|
92
95
|
|
93
|
-
def _redirect_response_error(redirect_url,
|
96
|
+
def _redirect_response_error(redirect_url, params)
|
94
97
|
response_mode = param_or_nil("response_mode") || oauth_response_mode
|
95
98
|
|
96
99
|
case response_mode
|
97
100
|
when "form_post"
|
98
101
|
response["Content-Type"] = "text/html"
|
99
|
-
|
100
|
-
|
101
|
-
<
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
query_params.map do |name, value|
|
106
|
-
"<input type=\"hidden\" name=\"#{name}\" value=\"#{scope.h(value)}\" />"
|
107
|
-
end.join
|
108
|
-
}
|
109
|
-
</form>
|
110
|
-
</body>
|
111
|
-
</html>
|
112
|
-
FORM
|
102
|
+
error_body = form_post_error_response_html(redirect_url) do
|
103
|
+
params.map do |name, value|
|
104
|
+
"<input type=\"hidden\" name=\"#{name}\" value=\"#{scope.h(value)}\" />"
|
105
|
+
end.join
|
106
|
+
end
|
107
|
+
response.write(error_body)
|
113
108
|
request.halt
|
114
109
|
else
|
115
110
|
super
|
116
111
|
end
|
117
112
|
end
|
118
113
|
|
114
|
+
def form_post_response_html(url)
|
115
|
+
<<-FORM
|
116
|
+
<html>
|
117
|
+
<head><title>Authorized</title></head>
|
118
|
+
<body onload="javascript:document.forms[0].submit()">
|
119
|
+
<form method="post" action="#{url}">
|
120
|
+
#{yield}
|
121
|
+
<input type="submit" class="btn btn-outline-primary" value="#{scope.h(oauth_authorize_post_button)}"/>
|
122
|
+
</form>
|
123
|
+
</body>
|
124
|
+
</html>
|
125
|
+
FORM
|
126
|
+
end
|
127
|
+
|
128
|
+
def form_post_error_response_html(url)
|
129
|
+
<<-FORM
|
130
|
+
<html>
|
131
|
+
<head><title></title></head>
|
132
|
+
<body onload="javascript:document.forms[0].submit()">
|
133
|
+
<form method="post" action="#{url}">
|
134
|
+
#{yield}
|
135
|
+
</form>
|
136
|
+
</body>
|
137
|
+
</html>
|
138
|
+
FORM
|
139
|
+
end
|
140
|
+
|
119
141
|
def create_token(grant_type)
|
120
142
|
return super unless supported_grant_type?(grant_type, "authorization_code")
|
121
143
|
|
@@ -92,6 +92,14 @@ module Rodauth
|
|
92
92
|
try_approval_prompt if use_oauth_access_type? && request.get?
|
93
93
|
|
94
94
|
redirect_response_error("invalid_scope") if (request.post? || param_or_nil("scope")) && !check_valid_scopes?
|
95
|
+
|
96
|
+
response_mode = param_or_nil("response_mode")
|
97
|
+
|
98
|
+
redirect_response_error("invalid_request") unless response_mode.nil? || oauth_response_modes_supported.include?(response_mode)
|
99
|
+
end
|
100
|
+
|
101
|
+
def check_valid_scopes?(scp = scopes)
|
102
|
+
super(scp - %w[offline_access])
|
95
103
|
end
|
96
104
|
|
97
105
|
def check_valid_response_type?
|
@@ -762,31 +762,31 @@ module Rodauth
|
|
762
762
|
throw_json_response_error(status_code, error_code)
|
763
763
|
else
|
764
764
|
redirect_url = URI.parse(redirect_url)
|
765
|
-
|
765
|
+
params = []
|
766
766
|
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
767
|
+
params << if respond_to?(:"oauth_#{error_code}_error_code")
|
768
|
+
["error", send(:"oauth_#{error_code}_error_code")]
|
769
|
+
else
|
770
|
+
["error", error_code]
|
771
|
+
end
|
772
772
|
|
773
773
|
if respond_to?(:"oauth_#{error_code}_message")
|
774
774
|
message = send(:"oauth_#{error_code}_message")
|
775
|
-
|
775
|
+
params << ["error_description", CGI.escape(message)]
|
776
776
|
end
|
777
777
|
|
778
778
|
state = param_or_nil("state")
|
779
779
|
|
780
|
-
|
780
|
+
params << ["state", state] if state
|
781
781
|
|
782
|
-
_redirect_response_error(redirect_url,
|
782
|
+
_redirect_response_error(redirect_url, params)
|
783
783
|
end
|
784
784
|
end
|
785
785
|
|
786
|
-
def _redirect_response_error(redirect_url,
|
787
|
-
|
788
|
-
|
789
|
-
redirect_url.query =
|
786
|
+
def _redirect_response_error(redirect_url, params)
|
787
|
+
params = params.map { |k, v| "#{k}=#{v}" }
|
788
|
+
params << redirect_url.query if redirect_url.query
|
789
|
+
redirect_url.query = params.join("&")
|
790
790
|
redirect(redirect_url.to_s)
|
791
791
|
end
|
792
792
|
|
@@ -841,10 +841,10 @@ module Rodauth
|
|
841
841
|
throw_json_response_error(oauth_authorization_required_error_status, "invalid_client")
|
842
842
|
end
|
843
843
|
|
844
|
-
def check_valid_scopes?
|
845
|
-
return false unless
|
844
|
+
def check_valid_scopes?(scp = scopes)
|
845
|
+
return false unless scp
|
846
846
|
|
847
|
-
(
|
847
|
+
(scp - oauth_application[oauth_applications_scopes_column].split(oauth_scope_separator)).empty?
|
848
848
|
end
|
849
849
|
|
850
850
|
def check_valid_uri?(uri)
|
@@ -118,8 +118,8 @@ module Rodauth
|
|
118
118
|
}
|
119
119
|
end
|
120
120
|
|
121
|
-
def validate_client_registration_params
|
122
|
-
@oauth_application_params =
|
121
|
+
def validate_client_registration_params(request_params = request.params)
|
122
|
+
@oauth_application_params = request_params.each_with_object({}) do |(key, value), params|
|
123
123
|
case key
|
124
124
|
when "redirect_uris"
|
125
125
|
if value.is_a?(Array)
|
@@ -152,7 +152,7 @@ module Rodauth
|
|
152
152
|
key = oauth_applications_grant_types_column
|
153
153
|
when "response_types"
|
154
154
|
if value.is_a?(Array)
|
155
|
-
grant_types =
|
155
|
+
grant_types = request_params["grant_types"] || %w[authorization_code]
|
156
156
|
value = value.each do |response_type|
|
157
157
|
unless oauth_response_types_supported.include?(response_type)
|
158
158
|
register_throw_json_response_error("invalid_client_metadata",
|
@@ -172,7 +172,7 @@ module Rodauth
|
|
172
172
|
when "client_uri"
|
173
173
|
key = oauth_applications_homepage_url_column
|
174
174
|
when "jwks_uri"
|
175
|
-
if
|
175
|
+
if request_params.key?("jwks")
|
176
176
|
register_throw_json_response_error("invalid_client_metadata",
|
177
177
|
register_invalid_jwks_param_message(key, "jwks"))
|
178
178
|
end
|
@@ -180,7 +180,7 @@ module Rodauth
|
|
180
180
|
key = __send__(:"oauth_applications_#{key}_column")
|
181
181
|
when "jwks"
|
182
182
|
register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(value)) unless value.is_a?(Hash)
|
183
|
-
if
|
183
|
+
if request_params.key?("jwks_uri")
|
184
184
|
register_throw_json_response_error("invalid_client_metadata",
|
185
185
|
register_invalid_jwks_param_message(key, "jwks_uri"))
|
186
186
|
end
|
@@ -205,14 +205,14 @@ module Rodauth
|
|
205
205
|
register_throw_json_response_error("invalid_client_metadata",
|
206
206
|
register_invalid_param_message(key))
|
207
207
|
end
|
208
|
-
|
208
|
+
request_params[key] = value = convert_to_boolean(key, value)
|
209
209
|
|
210
210
|
key = oauth_applications_require_pushed_authorization_requests_column
|
211
211
|
when "tls_client_certificate_bound_access_tokens"
|
212
212
|
property = :oauth_applications_tls_client_certificate_bound_access_tokens_column
|
213
213
|
register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(key)) unless respond_to?(property)
|
214
214
|
|
215
|
-
|
215
|
+
request_params[key] = value = convert_to_boolean(key, value)
|
216
216
|
|
217
217
|
key = oauth_applications_tls_client_certificate_bound_access_tokens_column
|
218
218
|
when /\Atls_client_auth_/
|
@@ -20,6 +20,24 @@ module Rodauth
|
|
20
20
|
|
21
21
|
private
|
22
22
|
|
23
|
+
def validate_authorize_params
|
24
|
+
super
|
25
|
+
|
26
|
+
response_mode = param_or_nil("response_mode")
|
27
|
+
|
28
|
+
return unless response_mode
|
29
|
+
|
30
|
+
response_type = param_or_nil("response_type")
|
31
|
+
|
32
|
+
return unless response_type == "token"
|
33
|
+
|
34
|
+
redirect_response_error("invalid_request") unless oauth_response_modes_for_token_supported.include?(response_mode)
|
35
|
+
end
|
36
|
+
|
37
|
+
def oauth_response_modes_for_token_supported
|
38
|
+
%w[fragment]
|
39
|
+
end
|
40
|
+
|
23
41
|
def do_authorize(response_params = {}, response_mode = param_or_nil("response_mode"))
|
24
42
|
response_type = param("response_type")
|
25
43
|
return super unless response_type == "token" && supported_response_type?(response_type)
|
@@ -48,13 +66,13 @@ module Rodauth
|
|
48
66
|
generate_token(grant_params, false)
|
49
67
|
end
|
50
68
|
|
51
|
-
def _redirect_response_error(redirect_url,
|
69
|
+
def _redirect_response_error(redirect_url, params)
|
52
70
|
response_types = param("response_type").split(/ +/)
|
53
71
|
|
54
72
|
return super if response_types.empty? || response_types == %w[code]
|
55
73
|
|
56
|
-
|
57
|
-
redirect_url.fragment =
|
74
|
+
params = params.map { |k, v| "#{k}=#{v}" }
|
75
|
+
redirect_url.fragment = params.join("&")
|
58
76
|
redirect(redirect_url.to_s)
|
59
77
|
end
|
60
78
|
|
@@ -62,7 +80,7 @@ module Rodauth
|
|
62
80
|
return super unless mode == "fragment"
|
63
81
|
|
64
82
|
redirect_url = URI.parse(redirect_uri)
|
65
|
-
params = params
|
83
|
+
params = [URI.encode_www_form(params)]
|
66
84
|
params << redirect_url.query if redirect_url.query
|
67
85
|
redirect_url.fragment = params.join("&")
|
68
86
|
redirect(redirect_url.to_s)
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rodauth/oauth"
|
4
|
+
|
5
|
+
module Rodauth
|
6
|
+
Feature.define(:oauth_jwt_secured_authorization_response_mode, :OauthJwtSecuredAuthorizationResponseMode) do
|
7
|
+
depends :oauth_authorize_base, :oauth_jwt_base
|
8
|
+
|
9
|
+
auth_value_method :oauth_authorization_response_mode_expires_in, 60 * 5 # 5 minutes
|
10
|
+
|
11
|
+
auth_value_method :oauth_applications_authorization_signed_response_alg_column, :authorization_signed_response_alg
|
12
|
+
auth_value_method :oauth_applications_authorization_encrypted_response_alg_column, :authorization_encrypted_response_alg
|
13
|
+
auth_value_method :oauth_applications_authorization_encrypted_response_enc_column, :authorization_encrypted_response_enc
|
14
|
+
|
15
|
+
auth_value_methods(
|
16
|
+
:authorization_signing_alg_values_supported,
|
17
|
+
:authorization_encryption_alg_values_supported,
|
18
|
+
:authorization_encryption_enc_values_supported
|
19
|
+
)
|
20
|
+
|
21
|
+
def oauth_response_modes_supported
|
22
|
+
jwt_response_modes = %w[jwt]
|
23
|
+
jwt_response_modes.push("query.jwt", "form_post.jwt") if features.include?(:oauth_authorization_code_grant)
|
24
|
+
jwt_response_modes << "fragment.jwt" if features.include?(:oauth_implicit_grant)
|
25
|
+
|
26
|
+
super | jwt_response_modes
|
27
|
+
end
|
28
|
+
|
29
|
+
def authorization_signing_alg_values_supported
|
30
|
+
oauth_jwt_jws_algorithms_supported
|
31
|
+
end
|
32
|
+
|
33
|
+
def authorization_encryption_alg_values_supported
|
34
|
+
oauth_jwt_jwe_algorithms_supported
|
35
|
+
end
|
36
|
+
|
37
|
+
def authorization_encryption_enc_values_supported
|
38
|
+
oauth_jwt_jwe_encryption_methods_supported
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def oauth_response_modes_for_code_supported
|
44
|
+
return [] unless features.include?(:oauth_authorization_code_grant)
|
45
|
+
|
46
|
+
super | %w[query.jwt form_post.jwt jwt]
|
47
|
+
end
|
48
|
+
|
49
|
+
def oauth_response_modes_for_token_supported
|
50
|
+
return [] unless features.include?(:oauth_implicit_grant)
|
51
|
+
|
52
|
+
super | %w[fragment.jwt jwt]
|
53
|
+
end
|
54
|
+
|
55
|
+
def authorize_response(params, mode)
|
56
|
+
return super unless mode.end_with?("jwt")
|
57
|
+
|
58
|
+
response_type = param_or_nil("response_type")
|
59
|
+
|
60
|
+
redirect_url = URI.parse(redirect_uri)
|
61
|
+
|
62
|
+
jwt = jwt_encode_authorization_response_mode(params)
|
63
|
+
|
64
|
+
if mode == "query.jwt" || (mode == "jwt" && response_type == "code")
|
65
|
+
return super unless features.include?(:oauth_authorization_code_grant)
|
66
|
+
|
67
|
+
params = ["response=#{CGI.escape(jwt)}"]
|
68
|
+
params << redirect_url.query if redirect_url.query
|
69
|
+
redirect_url.query = params.join("&")
|
70
|
+
redirect(redirect_url.to_s)
|
71
|
+
elsif mode == "form_post.jwt"
|
72
|
+
return super unless features.include?(:oauth_authorization_code_grant)
|
73
|
+
|
74
|
+
response["Content-Type"] = "text/html"
|
75
|
+
body = form_post_response_html(redirect_url) do
|
76
|
+
"<input type=\"hidden\" name=\"response\" value=\"#{scope.h(jwt)}\" />"
|
77
|
+
end
|
78
|
+
response.write(body)
|
79
|
+
request.halt
|
80
|
+
elsif mode == "fragment.jwt" || (mode == "jwt" && response_type == "token")
|
81
|
+
return super unless features.include?(:oauth_implicit_grant)
|
82
|
+
|
83
|
+
params = ["response=#{CGI.escape(jwt)}"]
|
84
|
+
params << redirect_url.query if redirect_url.query
|
85
|
+
redirect_url.fragment = params.join("&")
|
86
|
+
redirect(redirect_url.to_s)
|
87
|
+
else
|
88
|
+
super
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def _redirect_response_error(redirect_url, params)
|
93
|
+
response_mode = param_or_nil("response_mode")
|
94
|
+
return super unless response_mode.end_with?("jwt")
|
95
|
+
|
96
|
+
authorize_response(Hash[params], response_mode)
|
97
|
+
end
|
98
|
+
|
99
|
+
def jwt_encode_authorization_response_mode(params)
|
100
|
+
now = Time.now.to_i
|
101
|
+
claims = {
|
102
|
+
iss: oauth_jwt_issuer,
|
103
|
+
aud: oauth_application[oauth_applications_client_id_column],
|
104
|
+
exp: now + oauth_authorization_response_mode_expires_in,
|
105
|
+
iat: now
|
106
|
+
}.merge(params)
|
107
|
+
|
108
|
+
encode_params = {
|
109
|
+
jwks: oauth_application_jwks(oauth_application),
|
110
|
+
signing_algorithm: oauth_application[oauth_applications_authorization_signed_response_alg_column],
|
111
|
+
encryption_algorithm: oauth_application[oauth_applications_authorization_encrypted_response_alg_column],
|
112
|
+
encryption_method: oauth_application[oauth_applications_authorization_encrypted_response_enc_column]
|
113
|
+
}.compact
|
114
|
+
|
115
|
+
jwt_encode(claims, **encode_params)
|
116
|
+
end
|
117
|
+
|
118
|
+
def oauth_server_metadata_body(*)
|
119
|
+
super.tap do |data|
|
120
|
+
data[:authorization_signing_alg_values_supported] = authorization_signing_alg_values_supported
|
121
|
+
data[:authorization_encryption_alg_values_supported] = authorization_encryption_alg_values_supported
|
122
|
+
data[:authorization_encryption_enc_values_supported] = authorization_encryption_enc_values_supported
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -23,9 +23,7 @@ module Rodauth
|
|
23
23
|
classes += " disabled" if current || !page
|
24
24
|
classes += " active" if current
|
25
25
|
if page
|
26
|
-
params = request.GET.merge("page" => page)
|
27
|
-
v ? "#{CGI.escape(String(k))}=#{CGI.escape(String(v))}" : CGI.escape(String(k))
|
28
|
-
end.join("&")
|
26
|
+
params = URI.encode_www_form(request.GET.merge("page" => page))
|
29
27
|
|
30
28
|
href = "#{request.path}?#{params}"
|
31
29
|
|
@@ -63,7 +63,7 @@ module Rodauth
|
|
63
63
|
id_token_signing_alg_values_supported
|
64
64
|
].freeze
|
65
65
|
|
66
|
-
depends :account_expiration, :oauth_jwt, :oauth_jwt_jwks, :oauth_authorization_code_grant
|
66
|
+
depends :account_expiration, :oauth_jwt, :oauth_jwt_jwks, :oauth_authorization_code_grant, :oauth_implicit_grant
|
67
67
|
|
68
68
|
auth_value_method :oauth_application_scopes, %w[openid]
|
69
69
|
|
@@ -89,9 +89,16 @@ module Rodauth
|
|
89
89
|
auth_value_method :oauth_prompt_login_interval, 5 * 60 * 60 # 5 minutes
|
90
90
|
|
91
91
|
auth_value_methods(
|
92
|
+
:userinfo_signing_alg_values_supported,
|
93
|
+
:userinfo_encryption_alg_values_supported,
|
94
|
+
:userinfo_encryption_enc_values_supported,
|
95
|
+
:request_object_signing_alg_values_supported,
|
96
|
+
:request_object_encryption_alg_values_supported,
|
97
|
+
:request_object_encryption_enc_values_supported,
|
92
98
|
:oauth_acr_values_supported,
|
93
99
|
:get_oidc_account_last_login_at,
|
94
100
|
:oidc_authorize_on_prompt_none?,
|
101
|
+
:fill_with_account_claims,
|
95
102
|
:get_oidc_param,
|
96
103
|
:get_additional_param,
|
97
104
|
:require_acr_value_phr,
|
@@ -233,6 +240,30 @@ module Rodauth
|
|
233
240
|
end
|
234
241
|
end
|
235
242
|
|
243
|
+
def userinfo_signing_alg_values_supported
|
244
|
+
oauth_jwt_jws_algorithms_supported
|
245
|
+
end
|
246
|
+
|
247
|
+
def userinfo_encryption_alg_values_supported
|
248
|
+
oauth_jwt_jwe_algorithms_supported
|
249
|
+
end
|
250
|
+
|
251
|
+
def userinfo_encryption_enc_values_supported
|
252
|
+
oauth_jwt_jwe_encryption_methods_supported
|
253
|
+
end
|
254
|
+
|
255
|
+
def request_object_signing_alg_values_supported
|
256
|
+
oauth_jwt_jws_algorithms_supported
|
257
|
+
end
|
258
|
+
|
259
|
+
def request_object_encryption_alg_values_supported
|
260
|
+
oauth_jwt_jwe_algorithms_supported
|
261
|
+
end
|
262
|
+
|
263
|
+
def request_object_encryption_enc_values_supported
|
264
|
+
oauth_jwt_jwe_encryption_methods_supported
|
265
|
+
end
|
266
|
+
|
236
267
|
def oauth_acr_values_supported
|
237
268
|
acr_values = []
|
238
269
|
acr_values << "phrh" if features.include?(:webauthn_login)
|
@@ -274,29 +305,33 @@ module Rodauth
|
|
274
305
|
|
275
306
|
sc = scopes
|
276
307
|
|
277
|
-
|
278
|
-
|
308
|
+
# MUST ensure that the prompt parameter contains consent
|
309
|
+
# MUST ignore the offline_access request unless the Client
|
310
|
+
# is using a response_type value that would result in an
|
311
|
+
# Authorization Code
|
312
|
+
if sc && sc.include?("offline_access") && !(param_or_nil("prompt") == "consent" && (
|
313
|
+
(response_type = param_or_nil("response_type")) && response_type.split(" ").include?("code")
|
314
|
+
))
|
279
315
|
sc.delete("offline_access")
|
280
316
|
|
281
|
-
# MUST ensure that the prompt parameter contains consent
|
282
|
-
# MUST ignore the offline_access request unless the Client
|
283
|
-
# is using a response_type value that would result in an
|
284
|
-
# Authorization Code
|
285
|
-
if param_or_nil("prompt") == "consent" && (
|
286
|
-
(response_type = param_or_nil("response_type")) && response_type.split(" ").include?("code")
|
287
|
-
)
|
288
|
-
request.params["access_type"] = "offline"
|
289
|
-
end
|
290
|
-
|
291
317
|
request.params["scope"] = sc.join(" ")
|
292
318
|
end
|
293
319
|
|
294
320
|
super
|
295
321
|
|
296
|
-
|
297
|
-
|
322
|
+
response_type = param_or_nil("response_type")
|
323
|
+
|
324
|
+
is_id_token_response_type = response_type.include?("id_token")
|
325
|
+
|
326
|
+
redirect_response_error("invalid_request") if is_id_token_response_type && !param_or_nil("nonce")
|
327
|
+
|
328
|
+
return unless is_id_token_response_type || response_type == "code token"
|
329
|
+
|
330
|
+
response_mode = param_or_nil("response_mode")
|
331
|
+
|
332
|
+
# id_token: The default Response Mode for this Response Type is the fragment encoding and the query encoding MUST NOT be used.
|
298
333
|
|
299
|
-
redirect_response_error("invalid_request") unless
|
334
|
+
redirect_response_error("invalid_request") unless response_mode.nil? || response_mode == "fragment"
|
300
335
|
end
|
301
336
|
|
302
337
|
def require_authorizable_account
|
@@ -463,24 +498,7 @@ module Rodauth
|
|
463
498
|
signing_algorithm = oauth_application[oauth_applications_id_token_signed_response_alg_column] ||
|
464
499
|
oauth_jwt_keys.keys.first
|
465
500
|
|
466
|
-
|
467
|
-
|
468
|
-
id_token_claims[:nonce] = oauth_grant[oauth_grants_nonce_column] if oauth_grant[oauth_grants_nonce_column]
|
469
|
-
|
470
|
-
id_token_claims[:acr] = oauth_grant[oauth_grants_acr_column] if oauth_grant[oauth_grants_acr_column]
|
471
|
-
|
472
|
-
# Time when the End-User authentication occurred.
|
473
|
-
id_token_claims[:auth_time] = get_oidc_account_last_login_at(oauth_grant[oauth_grants_account_id_column]).to_i
|
474
|
-
|
475
|
-
# Access Token hash value.
|
476
|
-
if (access_token = oauth_grant[oauth_grants_token_column])
|
477
|
-
id_token_claims[:at_hash] = id_token_hash(access_token, signing_algorithm)
|
478
|
-
end
|
479
|
-
|
480
|
-
# code hash value.
|
481
|
-
if (code = oauth_grant[oauth_grants_code_column])
|
482
|
-
id_token_claims[:c_hash] = id_token_hash(code, signing_algorithm)
|
483
|
-
end
|
501
|
+
id_claims = id_token_claims(oauth_grant, signing_algorithm)
|
484
502
|
|
485
503
|
account = db[accounts_table].where(account_id_column => oauth_grant[oauth_grants_account_id_column]).first
|
486
504
|
|
@@ -500,7 +518,7 @@ module Rodauth
|
|
500
518
|
|
501
519
|
# 5.4 - However, when no Access Token is issued (which is the case for the response_type value id_token),
|
502
520
|
# the resulting Claims are returned in the ID Token.
|
503
|
-
fill_with_account_claims(
|
521
|
+
fill_with_account_claims(id_claims, account, oauth_scopes, param_or_nil("claims_locales")) if include_claims
|
504
522
|
|
505
523
|
params = {
|
506
524
|
jwks: oauth_application_jwks(oauth_application),
|
@@ -509,7 +527,30 @@ module Rodauth
|
|
509
527
|
encryption_method: oauth_application[oauth_applications_id_token_encrypted_response_enc_column]
|
510
528
|
}.compact
|
511
529
|
|
512
|
-
oauth_grant[:id_token] = jwt_encode(
|
530
|
+
oauth_grant[:id_token] = jwt_encode(id_claims, **params)
|
531
|
+
end
|
532
|
+
|
533
|
+
def id_token_claims(oauth_grant, signing_algorithm)
|
534
|
+
claims = jwt_claims(oauth_grant)
|
535
|
+
|
536
|
+
claims[:nonce] = oauth_grant[oauth_grants_nonce_column] if oauth_grant[oauth_grants_nonce_column]
|
537
|
+
|
538
|
+
claims[:acr] = oauth_grant[oauth_grants_acr_column] if oauth_grant[oauth_grants_acr_column]
|
539
|
+
|
540
|
+
# Time when the End-User authentication occurred.
|
541
|
+
claims[:auth_time] = get_oidc_account_last_login_at(oauth_grant[oauth_grants_account_id_column]).to_i
|
542
|
+
|
543
|
+
# Access Token hash value.
|
544
|
+
if (access_token = oauth_grant[oauth_grants_token_column])
|
545
|
+
claims[:at_hash] = id_token_hash(access_token, signing_algorithm)
|
546
|
+
end
|
547
|
+
|
548
|
+
# code hash value.
|
549
|
+
if (code = oauth_grant[oauth_grants_code_column])
|
550
|
+
claims[:c_hash] = id_token_hash(code, signing_algorithm)
|
551
|
+
end
|
552
|
+
|
553
|
+
claims
|
513
554
|
end
|
514
555
|
|
515
556
|
# aka fill_with_standard_claims
|
@@ -627,10 +668,9 @@ module Rodauth
|
|
627
668
|
|
628
669
|
def check_valid_response_type?
|
629
670
|
case param_or_nil("response_type")
|
630
|
-
when "none", "id_token", "code id_token" # multiple
|
671
|
+
when "none", "id_token", "code id_token", # multiple
|
672
|
+
"code token", "id_token token", "code id_token token"
|
631
673
|
true
|
632
|
-
when "code token", "id_token token", "code id_token token"
|
633
|
-
supports_token_response_type?
|
634
674
|
else
|
635
675
|
super
|
636
676
|
end
|
@@ -642,10 +682,6 @@ module Rodauth
|
|
642
682
|
param("response_type") == "none"
|
643
683
|
end
|
644
684
|
|
645
|
-
def supports_token_response_type?
|
646
|
-
features.include?(:oauth_implicit_grant)
|
647
|
-
end
|
648
|
-
|
649
685
|
def do_authorize(response_params = {}, response_mode = param_or_nil("response_mode"))
|
650
686
|
response_type = param("response_type")
|
651
687
|
case response_type
|
@@ -654,8 +690,6 @@ module Rodauth
|
|
654
690
|
generate_id_token(grant_params, true)
|
655
691
|
response_params.replace("id_token" => grant_params[:id_token])
|
656
692
|
when "code token"
|
657
|
-
redirect_response_error("invalid_request") unless supports_token_response_type?
|
658
|
-
|
659
693
|
response_params.replace(create_oauth_grant_with_token)
|
660
694
|
when "code id_token"
|
661
695
|
params = _do_authorize_code
|
@@ -666,16 +700,12 @@ module Rodauth
|
|
666
700
|
"code" => params["code"]
|
667
701
|
)
|
668
702
|
when "id_token token"
|
669
|
-
redirect_response_error("invalid_request") unless supports_token_response_type?
|
670
|
-
|
671
703
|
grant_params = oidc_grant_params.merge(oauth_grants_type_column => "hybrid")
|
672
704
|
oauth_grant = _do_authorize_token(grant_params)
|
673
705
|
generate_id_token(oauth_grant)
|
674
706
|
|
675
707
|
response_params.replace(json_access_token_payload(oauth_grant))
|
676
708
|
when "code id_token token"
|
677
|
-
redirect_response_error("invalid_request") unless supports_token_response_type?
|
678
|
-
|
679
709
|
params = create_oauth_grant_with_token
|
680
710
|
oauth_grant = valid_oauth_grant_ds.where(oauth_grants_code_column => params["code"]).first
|
681
711
|
oauth_grant[oauth_grants_token_column] = params["access_token"]
|
@@ -694,7 +724,8 @@ module Rodauth
|
|
694
724
|
grant_params = {
|
695
725
|
**resource_owner_params,
|
696
726
|
oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
|
697
|
-
oauth_grants_scopes_column => scopes.join(oauth_scope_separator)
|
727
|
+
oauth_grants_scopes_column => scopes.join(oauth_scope_separator),
|
728
|
+
oauth_grants_redirect_uri_column => param_or_nil("redirect_uri")
|
698
729
|
}
|
699
730
|
if (nonce = param_or_nil("nonce"))
|
700
731
|
grant_params[oauth_grants_nonce_column] = nonce
|
@@ -709,6 +740,12 @@ module Rodauth
|
|
709
740
|
grant_params
|
710
741
|
end
|
711
742
|
|
743
|
+
def generate_token(grant_params = {}, should_generate_refresh_token = true)
|
744
|
+
scopes = grant_params[oauth_grants_scopes_column].split(oauth_scope_separator)
|
745
|
+
|
746
|
+
super(grant_params, scopes.include?("offline_access") && should_generate_refresh_token)
|
747
|
+
end
|
748
|
+
|
712
749
|
def authorize_response(params, mode)
|
713
750
|
redirect_url = URI.parse(redirect_uri)
|
714
751
|
redirect(redirect_url.to_s) if mode == "none"
|
@@ -10,7 +10,7 @@ module Rodauth
|
|
10
10
|
|
11
11
|
private
|
12
12
|
|
13
|
-
def validate_client_registration_params
|
13
|
+
def validate_client_registration_params(*)
|
14
14
|
super
|
15
15
|
|
16
16
|
if (value = @oauth_application_params[oauth_applications_application_type_column])
|
@@ -174,6 +174,44 @@ module Rodauth
|
|
174
174
|
register_throw_json_response_error("invalid_client_metadata",
|
175
175
|
register_invalid_client_metadata_message("userinfo_encrypted_response_enc", value))
|
176
176
|
end
|
177
|
+
|
178
|
+
if features.include?(:oauth_jwt_secured_authorization_response_mode)
|
179
|
+
if defined?(oauth_applications_authorization_signed_response_alg_column) &&
|
180
|
+
(value = @oauth_application_params[oauth_applications_authorization_signed_response_alg_column]) &&
|
181
|
+
(!oauth_jwt_jws_algorithms_supported.include?(value) || value == "none")
|
182
|
+
register_throw_json_response_error("invalid_client_metadata",
|
183
|
+
register_invalid_client_metadata_message("authorization_signed_response_alg", value))
|
184
|
+
end
|
185
|
+
|
186
|
+
if defined?(oauth_applications_authorization_encrypted_response_alg_column) &&
|
187
|
+
(value = @oauth_application_params[oauth_applications_authorization_encrypted_response_alg_column]) &&
|
188
|
+
!oauth_jwt_jwe_algorithms_supported.include?(value)
|
189
|
+
register_throw_json_response_error("invalid_client_metadata",
|
190
|
+
register_invalid_client_metadata_message("authorization_encrypted_response_alg", value))
|
191
|
+
end
|
192
|
+
|
193
|
+
if defined?(oauth_applications_authorization_encrypted_response_enc_column)
|
194
|
+
if (value = @oauth_application_params[oauth_applications_authorization_encrypted_response_enc_column])
|
195
|
+
|
196
|
+
unless @oauth_application_params[oauth_applications_authorization_encrypted_response_alg_column]
|
197
|
+
# When authorization_encrypted_response_enc is included, authorization_encrypted_response_alg MUST also be provided.
|
198
|
+
register_throw_json_response_error("invalid_client_metadata",
|
199
|
+
register_invalid_client_metadata_message("authorization_encrypted_response_alg", value))
|
200
|
+
|
201
|
+
end
|
202
|
+
|
203
|
+
unless oauth_jwt_jwe_encryption_methods_supported.include?(value)
|
204
|
+
register_throw_json_response_error("invalid_client_metadata",
|
205
|
+
register_invalid_client_metadata_message("authorization_encrypted_response_enc", value))
|
206
|
+
end
|
207
|
+
elsif @oauth_application_params[oauth_applications_authorization_encrypted_response_alg_column]
|
208
|
+
# If authorization_encrypted_response_alg is specified, the default for this value is A128CBC-HS256.
|
209
|
+
@oauth_application_params[oauth_applications_authorization_encrypted_response_enc_column] = "A128CBC-HS256"
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
@oauth_application_params
|
177
215
|
end
|
178
216
|
|
179
217
|
def validate_client_registration_response_type(response_type, grant_types)
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rodauth/oauth"
|
4
|
+
|
5
|
+
module Rodauth
|
6
|
+
Feature.define(:oidc_self_issued, :OidcSelfIssued) do
|
7
|
+
depends :oidc, :oidc_dynamic_client_registration
|
8
|
+
|
9
|
+
auth_value_method :oauth_application_scopes, %w[openid profile email address phone]
|
10
|
+
auth_value_method :oauth_jwt_jws_algorithms_supported, %w[RS256]
|
11
|
+
|
12
|
+
SELF_ISSUED_DEFAULT_APPLICATION_PARAMS = {
|
13
|
+
"scope" => "openid profile email address phone",
|
14
|
+
"response_types" => ["id_token"],
|
15
|
+
"subject_type" => "pairwise",
|
16
|
+
"id_token_signed_response_alg" => "RS256",
|
17
|
+
"request_object_signing_alg" => "RS256",
|
18
|
+
"grant_types" => %w[implicit]
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
def oauth_application
|
22
|
+
return @oauth_application if defined?(@oauth_application)
|
23
|
+
|
24
|
+
return super unless (registration = param_or_nil("registration"))
|
25
|
+
|
26
|
+
# self-issued!
|
27
|
+
redirect_uri = param_or_nil("client_id")
|
28
|
+
|
29
|
+
registration_params = JSON.parse(registration)
|
30
|
+
|
31
|
+
registration_params = SELF_ISSUED_DEFAULT_APPLICATION_PARAMS.merge(registration_params)
|
32
|
+
|
33
|
+
client_params = validate_client_registration_params(registration_params)
|
34
|
+
|
35
|
+
request.params["redirect_uri"] = client_params[oauth_applications_client_id_column] = redirect_uri
|
36
|
+
client_params[oauth_applications_redirect_uri_column] ||= redirect_uri
|
37
|
+
|
38
|
+
@oauth_application = client_params
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def oauth_response_types_supported
|
44
|
+
%w[id_token]
|
45
|
+
end
|
46
|
+
|
47
|
+
def request_object_signing_alg_values_supported
|
48
|
+
%w[none RS256]
|
49
|
+
end
|
50
|
+
|
51
|
+
def id_token_claims(oauth_grant, signing_algorithm)
|
52
|
+
claims = super
|
53
|
+
|
54
|
+
return claims unless claims[:client_id] == oauth_grant[oauth_grants_redirect_uri_column]
|
55
|
+
|
56
|
+
# https://openid.net/specs/openid-connect-core-1_0.html#SelfIssued - 7.4
|
57
|
+
|
58
|
+
pub_key = oauth_jwt_public_keys[signing_algorithm]
|
59
|
+
pub_key = pub_key.first if pub_key.is_a?(Array)
|
60
|
+
claims[:sub_jwk] = sub_jwk = jwk_export(pub_key)
|
61
|
+
|
62
|
+
claims[:iss] = "https://self-issued.me"
|
63
|
+
|
64
|
+
claims[:aud] = oauth_grant[oauth_grants_redirect_uri_column]
|
65
|
+
|
66
|
+
jwk_thumbprint = jwk_thumbprint(sub_jwk)
|
67
|
+
|
68
|
+
claims[:sub] = Base64.urlsafe_encode64(jwk_thumbprint, padding: false)
|
69
|
+
|
70
|
+
claims
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/templates/authorize.str
CHANGED
@@ -88,6 +88,7 @@
|
|
88
88
|
#{"<input type=\"hidden\" name=\"claims_locales\" value=\"#{rodauth.param("claims_locales")}\"/>" if rodauth.features.include?(:oidc) && rodauth.param_or_nil("claims_locales")}
|
89
89
|
#{"<input type=\"hidden\" name=\"claims\" value=\"#{h(rodauth.param("claims"))}\"/>" if rodauth.features.include?(:oidc) && rodauth.param_or_nil("claims")}
|
90
90
|
#{"<input type=\"hidden\" name=\"acr_values\" value=\"#{rodauth.param("acr_values")}\"/>" if rodauth.features.include?(:oidc) && rodauth.param_or_nil("acr_values")}
|
91
|
+
#{"<input type=\"hidden\" name=\"registration\" value=\"#{h(rodauth.param("registration"))}\"/>" if rodauth.features.include?(:oidc_self_issued) && rodauth.param_or_nil("registration")}
|
91
92
|
#{
|
92
93
|
if rodauth.features.include?(:oauth_resource_indicators) && rodauth.resource_indicators
|
93
94
|
rodauth.resource_indicators.map do |resource|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rodauth-oauth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tiago Cardoso
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-04-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rodauth
|
@@ -69,6 +69,7 @@ extra_rdoc_files:
|
|
69
69
|
- doc/release_notes/1_0_0.md
|
70
70
|
- doc/release_notes/1_1_0.md
|
71
71
|
- doc/release_notes/1_2_0.md
|
72
|
+
- doc/release_notes/1_3_0.md
|
72
73
|
files:
|
73
74
|
- CHANGELOG.md
|
74
75
|
- LICENSE.txt
|
@@ -109,6 +110,7 @@ files:
|
|
109
110
|
- doc/release_notes/1_0_0.md
|
110
111
|
- doc/release_notes/1_1_0.md
|
111
112
|
- doc/release_notes/1_2_0.md
|
113
|
+
- doc/release_notes/1_3_0.md
|
112
114
|
- lib/generators/rodauth/oauth/install_generator.rb
|
113
115
|
- lib/generators/rodauth/oauth/templates/app/models/oauth_application.rb
|
114
116
|
- lib/generators/rodauth/oauth/templates/app/models/oauth_grant.rb
|
@@ -138,6 +140,7 @@ files:
|
|
138
140
|
- lib/rodauth/features/oauth_jwt_bearer_grant.rb
|
139
141
|
- lib/rodauth/features/oauth_jwt_jwks.rb
|
140
142
|
- lib/rodauth/features/oauth_jwt_secured_authorization_request.rb
|
143
|
+
- lib/rodauth/features/oauth_jwt_secured_authorization_response_mode.rb
|
141
144
|
- lib/rodauth/features/oauth_management_base.rb
|
142
145
|
- lib/rodauth/features/oauth_pkce.rb
|
143
146
|
- lib/rodauth/features/oauth_pushed_authorization_request.rb
|
@@ -150,6 +153,7 @@ files:
|
|
150
153
|
- lib/rodauth/features/oidc.rb
|
151
154
|
- lib/rodauth/features/oidc_dynamic_client_registration.rb
|
152
155
|
- lib/rodauth/features/oidc_rp_initiated_logout.rb
|
156
|
+
- lib/rodauth/features/oidc_self_issued.rb
|
153
157
|
- lib/rodauth/oauth.rb
|
154
158
|
- lib/rodauth/oauth/database_extensions.rb
|
155
159
|
- lib/rodauth/oauth/http_extensions.rb
|