rodauth-oauth 0.10.4 → 1.0.0.pre.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/MIGRATION-GUIDE-v1.md +286 -0
- data/README.md +22 -30
- data/doc/release_notes/1_0_0_beta1.md +38 -0
- data/lib/generators/rodauth/oauth/install_generator.rb +0 -1
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +4 -6
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_search.html.erb +1 -1
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_verification.html.erb +2 -2
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb +1 -6
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application.html.erb +0 -2
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_grants.html.erb +41 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +2 -2
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_grants.html.erb +37 -0
- data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +18 -29
- data/lib/rodauth/features/oauth_application_management.rb +59 -72
- data/lib/rodauth/features/oauth_assertion_base.rb +19 -23
- data/lib/rodauth/features/oauth_authorization_code_grant.rb +35 -88
- data/lib/rodauth/features/oauth_authorize_base.rb +103 -20
- data/lib/rodauth/features/oauth_base.rb +365 -302
- data/lib/rodauth/features/oauth_client_credentials_grant.rb +20 -18
- data/lib/rodauth/features/{oauth_device_grant.rb → oauth_device_code_grant.rb} +62 -73
- data/lib/rodauth/features/oauth_dynamic_client_registration.rb +46 -28
- data/lib/rodauth/features/oauth_grant_management.rb +70 -0
- data/lib/rodauth/features/oauth_implicit_grant.rb +25 -24
- data/lib/rodauth/features/oauth_jwt.rb +52 -688
- data/lib/rodauth/features/oauth_jwt_base.rb +435 -0
- data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +45 -17
- data/lib/rodauth/features/oauth_jwt_jwks.rb +47 -0
- data/lib/rodauth/features/oauth_jwt_secured_authorization_request.rb +62 -0
- data/lib/rodauth/features/oauth_management_base.rb +2 -0
- data/lib/rodauth/features/oauth_pkce.rb +22 -26
- data/lib/rodauth/features/oauth_resource_indicators.rb +33 -21
- data/lib/rodauth/features/oauth_resource_server.rb +59 -0
- data/lib/rodauth/features/oauth_saml_bearer_grant.rb +5 -1
- data/lib/rodauth/features/oauth_token_introspection.rb +76 -46
- data/lib/rodauth/features/oauth_token_revocation.rb +46 -33
- data/lib/rodauth/features/oidc.rb +188 -95
- data/lib/rodauth/features/oidc_dynamic_client_registration.rb +89 -53
- data/lib/rodauth/oauth/database_extensions.rb +8 -6
- data/lib/rodauth/oauth/http_extensions.rb +61 -0
- data/lib/rodauth/oauth/railtie.rb +20 -0
- data/lib/rodauth/oauth/version.rb +1 -1
- data/lib/rodauth/oauth.rb +29 -1
- data/locales/en.yml +32 -22
- data/locales/pt.yml +32 -22
- data/templates/authorize.str +19 -24
- data/templates/device_search.str +1 -1
- data/templates/device_verification.str +2 -2
- data/templates/jwks_field.str +1 -0
- data/templates/new_oauth_application.str +1 -2
- data/templates/oauth_application.str +2 -2
- data/templates/oauth_application_oauth_grants.str +54 -0
- data/templates/oauth_applications.str +2 -2
- data/templates/oauth_grants.str +52 -0
- metadata +20 -16
- data/lib/generators/rodauth/oauth/templates/app/models/oauth_token.rb +0 -4
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_tokens.html.erb +0 -39
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_tokens.html.erb +0 -35
- data/lib/rodauth/features/oauth.rb +0 -9
- data/lib/rodauth/features/oauth_http_mac.rb +0 -86
- data/lib/rodauth/features/oauth_token_management.rb +0 -81
- data/lib/rodauth/oauth/refinements.rb +0 -48
- data/templates/jwt_public_key_field.str +0 -4
- data/templates/oauth_application_oauth_tokens.str +0 -52
- data/templates/oauth_tokens.str +0 -50
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "rodauth/oauth"
|
4
|
+
|
3
5
|
module Rodauth
|
4
6
|
Feature.define(:oidc, :Oidc) do
|
5
7
|
# https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
|
@@ -60,26 +62,27 @@ module Rodauth
|
|
60
62
|
id_token_signing_alg_values_supported
|
61
63
|
].freeze
|
62
64
|
|
63
|
-
depends :account_expiration, :oauth_jwt
|
65
|
+
depends :account_expiration, :oauth_jwt, :oauth_jwt_jwks, :oauth_authorization_code_grant
|
64
66
|
|
65
|
-
auth_value_method :oauth_application_default_scope, "openid"
|
66
67
|
auth_value_method :oauth_application_scopes, %w[openid]
|
67
68
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
69
|
+
%i[
|
70
|
+
subject_type application_type sector_identifier_uri
|
71
|
+
id_token_signed_response_alg id_token_encrypted_response_alg id_token_encrypted_response_enc
|
72
|
+
userinfo_signed_response_alg userinfo_encrypted_response_alg userinfo_encrypted_response_enc
|
73
|
+
].each do |column|
|
74
|
+
auth_value_method :"oauth_applications_#{column}_column", column
|
75
|
+
end
|
74
76
|
|
75
77
|
auth_value_method :oauth_grants_nonce_column, :nonce
|
76
78
|
auth_value_method :oauth_grants_acr_column, :acr
|
77
|
-
auth_value_method :
|
78
|
-
auth_value_method :
|
79
|
+
auth_value_method :oauth_grants_nonce_column, :nonce
|
80
|
+
auth_value_method :oauth_grants_acr_column, :acr
|
79
81
|
|
80
|
-
|
82
|
+
auth_value_method :oauth_jwt_subject_type, "public" # fallback subject type: public, pairwise
|
83
|
+
auth_value_method :oauth_jwt_subject_secret, nil # salt for pairwise generation
|
81
84
|
|
82
|
-
|
85
|
+
translatable_method :oauth_invalid_scope_message, "The Access Token expired"
|
83
86
|
|
84
87
|
auth_value_method :oauth_prompt_login_cookie_key, "_rodauth_oauth_prompt_login"
|
85
88
|
auth_value_method :oauth_prompt_login_cookie_options, {}.freeze
|
@@ -90,42 +93,42 @@ module Rodauth
|
|
90
93
|
auth_value_method :use_rp_initiated_logout?, false
|
91
94
|
|
92
95
|
auth_value_methods(
|
96
|
+
:get_oidc_account_last_login_at,
|
93
97
|
:get_oidc_param,
|
94
98
|
:get_additional_param,
|
95
99
|
:require_acr_value_phr,
|
96
100
|
:require_acr_value_phrh,
|
97
|
-
:require_acr_value
|
101
|
+
:require_acr_value,
|
102
|
+
:json_webfinger_payload
|
98
103
|
)
|
99
104
|
|
100
105
|
# /userinfo
|
101
|
-
|
102
|
-
next unless is_authorization_server?
|
103
|
-
|
106
|
+
auth_server_route(:userinfo) do |r|
|
104
107
|
r.on method: %i[get post] do
|
105
108
|
catch_error do
|
106
|
-
|
109
|
+
claims = authorization_token
|
107
110
|
|
108
|
-
throw_json_response_error(
|
111
|
+
throw_json_response_error(oauth_authorization_required_error_status, "invalid_token") unless claims
|
109
112
|
|
110
|
-
oauth_scopes =
|
113
|
+
oauth_scopes = claims["scope"].split(" ")
|
111
114
|
|
112
|
-
throw_json_response_error(
|
115
|
+
throw_json_response_error(oauth_authorization_required_error_status, "invalid_token") unless oauth_scopes.include?("openid")
|
113
116
|
|
114
|
-
account = db[accounts_table].where(account_id_column =>
|
117
|
+
account = db[accounts_table].where(account_id_column => claims["sub"]).first
|
115
118
|
|
116
|
-
throw_json_response_error(
|
119
|
+
throw_json_response_error(oauth_authorization_required_error_status, "invalid_token") unless account
|
117
120
|
|
118
121
|
oauth_scopes.delete("openid")
|
119
122
|
|
120
|
-
oidc_claims = { "sub" =>
|
123
|
+
oidc_claims = { "sub" => claims["sub"] }
|
121
124
|
|
122
125
|
fill_with_account_claims(oidc_claims, account, oauth_scopes)
|
123
126
|
|
124
|
-
@oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column =>
|
127
|
+
@oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => claims["client_id"]).first
|
125
128
|
|
126
129
|
if (algo = @oauth_application && @oauth_application[oauth_applications_userinfo_signed_response_alg_column])
|
127
130
|
params = {
|
128
|
-
jwks: oauth_application_jwks,
|
131
|
+
jwks: oauth_application_jwks(@oauth_application),
|
129
132
|
encryption_algorithm: @oauth_application[oauth_applications_userinfo_encrypted_response_alg_column],
|
130
133
|
encryption_method: @oauth_application[oauth_applications_userinfo_encrypted_response_enc_column]
|
131
134
|
}.compact
|
@@ -141,16 +144,16 @@ module Rodauth
|
|
141
144
|
end
|
142
145
|
end
|
143
146
|
|
144
|
-
throw_json_response_error(
|
147
|
+
throw_json_response_error(oauth_authorization_required_error_status, "invalid_token")
|
145
148
|
end
|
146
149
|
end
|
147
150
|
|
148
151
|
# /oidc-logout
|
149
|
-
|
152
|
+
auth_server_route(:oidc_logout) do |r|
|
150
153
|
next unless use_rp_initiated_logout?
|
151
154
|
|
152
|
-
before_oidc_logout_route
|
153
155
|
require_authorizable_account
|
156
|
+
before_oidc_logout_route
|
154
157
|
|
155
158
|
# OpenID Providers MUST support the use of the HTTP GET and POST methods
|
156
159
|
r.on method: %i[get post] do
|
@@ -165,17 +168,21 @@ module Rodauth
|
|
165
168
|
# beforehand. Hence, we have to do it twice: decode-and-do-not-verify, initialize
|
166
169
|
# the @oauth_application, and then decode-and-verify.
|
167
170
|
#
|
168
|
-
|
169
|
-
|
171
|
+
claims = jwt_decode(param("id_token_hint"), verify_claims: false)
|
172
|
+
oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => claims["client_id"]).first
|
173
|
+
oauth_grant = db[oauth_grants_table]
|
174
|
+
.where(
|
175
|
+
oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
|
176
|
+
oauth_grants_account_id_column => account_id
|
177
|
+
).first
|
170
178
|
|
171
179
|
# check whether ID token belongs to currently logged-in user
|
172
|
-
redirect_response_error("invalid_request") unless
|
173
|
-
|
174
|
-
oauth_tokens_oauth_application_id_column => oauth_application_id
|
180
|
+
redirect_response_error("invalid_request") unless oauth_grant && claims["sub"] == jwt_subject(
|
181
|
+
oauth_grant, oauth_application
|
175
182
|
)
|
176
183
|
|
177
184
|
# When an id_token_hint parameter is present, the OP MUST validate that it was the issuer of the ID Token.
|
178
|
-
redirect_response_error("invalid_request") unless
|
185
|
+
redirect_response_error("invalid_request") unless claims && claims["iss"] == oauth_jwt_issuer
|
179
186
|
|
180
187
|
# now let's logout from IdP
|
181
188
|
transaction do
|
@@ -186,7 +193,7 @@ module Rodauth
|
|
186
193
|
|
187
194
|
if (post_logout_redirect_uri = param_or_nil("post_logout_redirect_uri"))
|
188
195
|
catch(:default_logout_redirect) do
|
189
|
-
oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column =>
|
196
|
+
oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => claims["client_id"]).first
|
190
197
|
|
191
198
|
throw(:default_logout_redirect) unless oauth_application
|
192
199
|
|
@@ -216,17 +223,19 @@ module Rodauth
|
|
216
223
|
end
|
217
224
|
end
|
218
225
|
|
219
|
-
def
|
226
|
+
def load_openid_configuration_route(alt_issuer = nil)
|
220
227
|
request.on(".well-known/openid-configuration") do
|
221
228
|
allow_cors(request)
|
222
229
|
|
223
|
-
request.
|
224
|
-
|
230
|
+
request.is do
|
231
|
+
request.get do
|
232
|
+
json_response_success(openid_configuration_body(alt_issuer), cache: true)
|
233
|
+
end
|
225
234
|
end
|
226
235
|
end
|
227
236
|
end
|
228
237
|
|
229
|
-
def
|
238
|
+
def load_webfinger_route
|
230
239
|
request.on(".well-known/webfinger") do
|
231
240
|
request.get do
|
232
241
|
resource = param_or_nil("resource")
|
@@ -236,14 +245,7 @@ module Rodauth
|
|
236
245
|
response.status = 200
|
237
246
|
response["Content-Type"] ||= "application/jrd+json"
|
238
247
|
|
239
|
-
|
240
|
-
subject: resource,
|
241
|
-
links: [{
|
242
|
-
rel: webfinger_relation,
|
243
|
-
href: authorization_server_url
|
244
|
-
}]
|
245
|
-
})
|
246
|
-
return_response(json_payload)
|
248
|
+
return_response(json_webfinger_payload)
|
247
249
|
end
|
248
250
|
end
|
249
251
|
end
|
@@ -257,6 +259,16 @@ module Rodauth
|
|
257
259
|
end
|
258
260
|
end
|
259
261
|
|
262
|
+
def oauth_response_types_supported
|
263
|
+
super | %w[id_token none]
|
264
|
+
end
|
265
|
+
|
266
|
+
def current_oauth_account
|
267
|
+
subject_type = current_oauth_application[oauth_applications_subject_type_column] || oauth_jwt_subject_type
|
268
|
+
|
269
|
+
return super unless subject_type == "pairwise"
|
270
|
+
end
|
271
|
+
|
260
272
|
private
|
261
273
|
|
262
274
|
if defined?(::I18n)
|
@@ -279,7 +291,7 @@ module Rodauth
|
|
279
291
|
|
280
292
|
redirect_response_error("invalid_request") unless max_age.positive?
|
281
293
|
|
282
|
-
if Time.now -
|
294
|
+
if Time.now - get_oidc_account_last_login_at(session_value) > max_age
|
283
295
|
# force user to re-login
|
284
296
|
clear_session
|
285
297
|
set_session_value(login_redirect_session_key, request.fullpath)
|
@@ -295,6 +307,41 @@ module Rodauth
|
|
295
307
|
try_acr_values
|
296
308
|
end
|
297
309
|
|
310
|
+
def get_oidc_account_last_login_at(account_id)
|
311
|
+
get_activity_timestamp(account_id, account_activity_last_activity_column)
|
312
|
+
end
|
313
|
+
|
314
|
+
def jwt_subject(oauth_grant, client_application = oauth_application)
|
315
|
+
subject_type = client_application[oauth_applications_subject_type_column] || oauth_jwt_subject_type
|
316
|
+
|
317
|
+
case subject_type
|
318
|
+
when "public"
|
319
|
+
super
|
320
|
+
when "pairwise"
|
321
|
+
identifier_uri = client_application[oauth_applications_sector_identifier_uri_column]
|
322
|
+
|
323
|
+
unless identifier_uri
|
324
|
+
identifier_uri = client_application[oauth_applications_redirect_uri_column]
|
325
|
+
identifier_uri = identifier_uri.split(" ")
|
326
|
+
# If the Client has not provided a value for sector_identifier_uri in Dynamic Client Registration
|
327
|
+
# [OpenID.Registration], the Sector Identifier used for pairwise identifier calculation is the host
|
328
|
+
# component of the registered redirect_uri. If there are multiple hostnames in the registered redirect_uris,
|
329
|
+
# the Client MUST register a sector_identifier_uri.
|
330
|
+
if identifier_uri.size > 1
|
331
|
+
# return error message
|
332
|
+
end
|
333
|
+
identifier_uri = identifier_uri.first
|
334
|
+
end
|
335
|
+
|
336
|
+
identifier_uri = URI(identifier_uri).host
|
337
|
+
|
338
|
+
account_id = oauth_grant[oauth_grants_account_id_column]
|
339
|
+
Digest::SHA256.hexdigest("#{identifier_uri}#{account_id}#{oauth_jwt_subject_secret}")
|
340
|
+
else
|
341
|
+
raise StandardError, "unexpected subject (#{subject_type})"
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
298
345
|
# this executes before checking for a logged in account
|
299
346
|
def try_prompt
|
300
347
|
return unless (prompt = param_or_nil("prompt"))
|
@@ -387,34 +434,27 @@ module Rodauth
|
|
387
434
|
super
|
388
435
|
end
|
389
436
|
|
390
|
-
def
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
super
|
395
|
-
end
|
396
|
-
|
397
|
-
def create_oauth_token(*)
|
398
|
-
oauth_token = super
|
399
|
-
generate_id_token(oauth_token)
|
400
|
-
oauth_token
|
437
|
+
def create_token(*)
|
438
|
+
oauth_grant = super
|
439
|
+
generate_id_token(oauth_grant)
|
440
|
+
oauth_grant
|
401
441
|
end
|
402
442
|
|
403
|
-
def generate_id_token(
|
404
|
-
oauth_scopes =
|
443
|
+
def generate_id_token(oauth_grant)
|
444
|
+
oauth_scopes = oauth_grant[oauth_grants_scopes_column].split(oauth_scope_separator)
|
405
445
|
|
406
446
|
return unless oauth_scopes.include?("openid")
|
407
447
|
|
408
|
-
id_token_claims = jwt_claims(
|
448
|
+
id_token_claims = jwt_claims(oauth_grant)
|
409
449
|
|
410
|
-
id_token_claims[:nonce] =
|
450
|
+
id_token_claims[:nonce] = oauth_grant[oauth_grants_nonce_column] if oauth_grant[oauth_grants_nonce_column]
|
411
451
|
|
412
|
-
id_token_claims[:acr] =
|
452
|
+
id_token_claims[:acr] = oauth_grant[oauth_grants_acr_column] if oauth_grant[oauth_grants_acr_column]
|
413
453
|
|
414
454
|
# Time when the End-User authentication occurred.
|
415
|
-
id_token_claims[:auth_time] =
|
455
|
+
id_token_claims[:auth_time] = get_oidc_account_last_login_at(oauth_grant[oauth_grants_account_id_column]).to_i
|
416
456
|
|
417
|
-
account = db[accounts_table].where(account_id_column =>
|
457
|
+
account = db[accounts_table].where(account_id_column => oauth_grant[oauth_grants_account_id_column]).first
|
418
458
|
|
419
459
|
# this should never happen!
|
420
460
|
# a newly minted oauth token from a grant should have been assigned to an account
|
@@ -424,13 +464,16 @@ module Rodauth
|
|
424
464
|
fill_with_account_claims(id_token_claims, account, oauth_scopes)
|
425
465
|
|
426
466
|
params = {
|
427
|
-
jwks: oauth_application_jwks,
|
428
|
-
signing_algorithm:
|
467
|
+
jwks: oauth_application_jwks(oauth_application),
|
468
|
+
signing_algorithm: (
|
469
|
+
oauth_application[oauth_applications_id_token_signed_response_alg_column] ||
|
470
|
+
oauth_jwt_keys.keys.first
|
471
|
+
),
|
429
472
|
encryption_algorithm: oauth_application[oauth_applications_id_token_encrypted_response_alg_column],
|
430
473
|
encryption_method: oauth_application[oauth_applications_id_token_encrypted_response_enc_column]
|
431
474
|
}.compact
|
432
475
|
|
433
|
-
|
476
|
+
oauth_grant[:id_token] = jwt_encode(id_token_claims, **params)
|
434
477
|
end
|
435
478
|
|
436
479
|
# aka fill_with_standard_claims
|
@@ -507,9 +550,9 @@ module Rodauth
|
|
507
550
|
end
|
508
551
|
end
|
509
552
|
|
510
|
-
def json_access_token_payload(
|
553
|
+
def json_access_token_payload(oauth_grant)
|
511
554
|
payload = super
|
512
|
-
payload["id_token"] =
|
555
|
+
payload["id_token"] = oauth_grant[:id_token] if oauth_grant[:id_token]
|
513
556
|
payload
|
514
557
|
end
|
515
558
|
|
@@ -517,31 +560,45 @@ module Rodauth
|
|
517
560
|
|
518
561
|
def check_valid_response_type?
|
519
562
|
case param_or_nil("response_type")
|
520
|
-
when "none", "id_token",
|
521
|
-
"code token", "code id_token", "id_token token", "code id_token token" # multiple
|
563
|
+
when "none", "id_token", "code id_token" # multiple
|
522
564
|
true
|
565
|
+
when "code token", "id_token token", "code id_token token"
|
566
|
+
supports_token_response_type?
|
523
567
|
else
|
524
568
|
super
|
525
569
|
end
|
526
570
|
end
|
527
571
|
|
528
|
-
def
|
529
|
-
return super unless
|
572
|
+
def supported_response_mode?(response_mode, *)
|
573
|
+
return super unless response_mode == "none"
|
574
|
+
|
575
|
+
param("response_type") == "none"
|
576
|
+
end
|
530
577
|
|
578
|
+
def supports_token_response_type?
|
579
|
+
features.include?(:oauth_implicit_grant)
|
580
|
+
end
|
581
|
+
|
582
|
+
def do_authorize(response_params = {}, response_mode = param_or_nil("response_mode"))
|
531
583
|
case param("response_type")
|
532
584
|
when "id_token"
|
533
585
|
response_params.replace(_do_authorize_id_token)
|
534
586
|
when "code token"
|
535
|
-
redirect_response_error("invalid_request") unless
|
587
|
+
redirect_response_error("invalid_request") unless supports_token_response_type?
|
536
588
|
|
537
589
|
response_params.replace(_do_authorize_code.merge(_do_authorize_token))
|
538
590
|
when "code id_token"
|
539
591
|
response_params.replace(_do_authorize_code.merge(_do_authorize_id_token))
|
540
592
|
when "id_token token"
|
593
|
+
redirect_response_error("invalid_request") unless supports_token_response_type?
|
594
|
+
|
541
595
|
response_params.replace(_do_authorize_id_token.merge(_do_authorize_token))
|
542
596
|
when "code id_token token"
|
597
|
+
redirect_response_error("invalid_request") unless supports_token_response_type?
|
543
598
|
|
544
599
|
response_params.replace(_do_authorize_code.merge(_do_authorize_id_token).merge(_do_authorize_token))
|
600
|
+
when "none"
|
601
|
+
response_mode ||= "none"
|
545
602
|
end
|
546
603
|
response_mode ||= "fragment" unless response_params.empty?
|
547
604
|
|
@@ -549,24 +606,30 @@ module Rodauth
|
|
549
606
|
end
|
550
607
|
|
551
608
|
def _do_authorize_id_token
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
609
|
+
grant_params = {
|
610
|
+
oauth_grants_account_id_column => account_id,
|
611
|
+
oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
|
612
|
+
oauth_grants_scopes_column => scopes.join(" ")
|
556
613
|
}
|
557
614
|
if (nonce = param_or_nil("nonce"))
|
558
|
-
|
615
|
+
grant_params[oauth_grants_nonce_column] = nonce
|
559
616
|
end
|
560
617
|
if (acr = param_or_nil("acr"))
|
561
|
-
|
618
|
+
grant_params[oauth_grants_acr_column] = acr
|
562
619
|
end
|
563
|
-
|
564
|
-
generate_id_token(
|
565
|
-
params = json_access_token_payload(
|
620
|
+
oauth_grant = generate_token(grant_params, false)
|
621
|
+
generate_id_token(oauth_grant)
|
622
|
+
params = json_access_token_payload(oauth_grant)
|
566
623
|
params.delete("access_token")
|
567
624
|
params
|
568
625
|
end
|
569
626
|
|
627
|
+
def authorize_response(params, mode)
|
628
|
+
redirect_url = URI.parse(redirect_uri)
|
629
|
+
redirect(redirect_url.to_s) if mode == "none"
|
630
|
+
super
|
631
|
+
end
|
632
|
+
|
570
633
|
# Logout
|
571
634
|
|
572
635
|
def validate_oidc_logout_params
|
@@ -579,6 +642,18 @@ module Rodauth
|
|
579
642
|
redirect_response_error("invalid_request")
|
580
643
|
end
|
581
644
|
|
645
|
+
# Webfinger
|
646
|
+
|
647
|
+
def json_webfinger_payload
|
648
|
+
JSON.dump({
|
649
|
+
subject: param("resource"),
|
650
|
+
links: [{
|
651
|
+
rel: "http://openid.net/specs/connect/1.0/issuer",
|
652
|
+
href: authorization_server_url
|
653
|
+
}]
|
654
|
+
})
|
655
|
+
end
|
656
|
+
|
582
657
|
# Metadata
|
583
658
|
|
584
659
|
def openid_configuration_body(path = nil)
|
@@ -600,27 +675,31 @@ module Rodauth
|
|
600
675
|
|
601
676
|
response_types_supported = metadata[:response_types_supported]
|
602
677
|
|
678
|
+
response_types_supported |= %w[none]
|
679
|
+
response_types_supported |= ["code id_token"] if metadata[:grant_types_supported].include?("authorization_code")
|
603
680
|
if metadata[:grant_types_supported].include?("implicit")
|
604
|
-
response_types_supported
|
681
|
+
response_types_supported |= ["code token", "id_token token", "code id_token token"]
|
605
682
|
end
|
606
683
|
|
684
|
+
alg_values, enc_values = oauth_jwt_jwe_keys.keys.transpose
|
685
|
+
|
607
686
|
metadata.merge(
|
608
687
|
userinfo_endpoint: userinfo_url,
|
609
688
|
end_session_endpoint: (oidc_logout_url if use_rp_initiated_logout?),
|
610
689
|
response_types_supported: response_types_supported,
|
611
|
-
subject_types_supported: [
|
690
|
+
subject_types_supported: %w[public pairwise],
|
612
691
|
|
613
692
|
id_token_signing_alg_values_supported: metadata[:token_endpoint_auth_signing_alg_values_supported],
|
614
|
-
id_token_encryption_alg_values_supported:
|
615
|
-
id_token_encryption_enc_values_supported:
|
693
|
+
id_token_encryption_alg_values_supported: Array(alg_values),
|
694
|
+
id_token_encryption_enc_values_supported: Array(enc_values),
|
616
695
|
|
617
|
-
userinfo_signing_alg_values_supported:
|
618
|
-
userinfo_encryption_alg_values_supported:
|
619
|
-
userinfo_encryption_enc_values_supported:
|
696
|
+
userinfo_signing_alg_values_supported: oauth_jwt_jws_algorithms_supported,
|
697
|
+
userinfo_encryption_alg_values_supported: oauth_jwt_jwe_algorithms_supported,
|
698
|
+
userinfo_encryption_enc_values_supported: oauth_jwt_jwe_encryption_methods_supported,
|
620
699
|
|
621
|
-
request_object_signing_alg_values_supported:
|
622
|
-
request_object_encryption_alg_values_supported:
|
623
|
-
request_object_encryption_enc_values_supported:
|
700
|
+
request_object_signing_alg_values_supported: oauth_jwt_jws_algorithms_supported,
|
701
|
+
request_object_encryption_alg_values_supported: oauth_jwt_jwe_algorithms_supported,
|
702
|
+
request_object_encryption_enc_values_supported: oauth_jwt_jwe_encryption_methods_supported,
|
624
703
|
|
625
704
|
# These Claim Types are described in Section 5.6 of OpenID Connect Core 1.0 [OpenID.Core].
|
626
705
|
# Values defined by this specification are normal, aggregated, and distributed.
|
@@ -644,5 +723,19 @@ module Rodauth
|
|
644
723
|
response.status = 200
|
645
724
|
return_response
|
646
725
|
end
|
726
|
+
|
727
|
+
def jwt_response_success(jwt, cache = false)
|
728
|
+
response.status = 200
|
729
|
+
response["Content-Type"] ||= "application/jwt"
|
730
|
+
if cache
|
731
|
+
# defaulting to 1-day for everyone, for now at least
|
732
|
+
max_age = 60 * 60 * 24
|
733
|
+
response["Cache-Control"] = "private, max-age=#{max_age}"
|
734
|
+
else
|
735
|
+
response["Cache-Control"] = "no-store"
|
736
|
+
response["Pragma"] = "no-cache"
|
737
|
+
end
|
738
|
+
return_response(jwt)
|
739
|
+
end
|
647
740
|
end
|
648
741
|
end
|