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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/MIGRATION-GUIDE-v1.md +286 -0
  3. data/README.md +22 -30
  4. data/doc/release_notes/1_0_0_beta1.md +38 -0
  5. data/lib/generators/rodauth/oauth/install_generator.rb +0 -1
  6. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +4 -6
  7. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_search.html.erb +1 -1
  8. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_verification.html.erb +2 -2
  9. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb +1 -6
  10. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application.html.erb +0 -2
  11. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_grants.html.erb +41 -0
  12. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +2 -2
  13. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_grants.html.erb +37 -0
  14. data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +18 -29
  15. data/lib/rodauth/features/oauth_application_management.rb +59 -72
  16. data/lib/rodauth/features/oauth_assertion_base.rb +19 -23
  17. data/lib/rodauth/features/oauth_authorization_code_grant.rb +35 -88
  18. data/lib/rodauth/features/oauth_authorize_base.rb +103 -20
  19. data/lib/rodauth/features/oauth_base.rb +365 -302
  20. data/lib/rodauth/features/oauth_client_credentials_grant.rb +20 -18
  21. data/lib/rodauth/features/{oauth_device_grant.rb → oauth_device_code_grant.rb} +62 -73
  22. data/lib/rodauth/features/oauth_dynamic_client_registration.rb +46 -28
  23. data/lib/rodauth/features/oauth_grant_management.rb +70 -0
  24. data/lib/rodauth/features/oauth_implicit_grant.rb +25 -24
  25. data/lib/rodauth/features/oauth_jwt.rb +52 -688
  26. data/lib/rodauth/features/oauth_jwt_base.rb +435 -0
  27. data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +45 -17
  28. data/lib/rodauth/features/oauth_jwt_jwks.rb +47 -0
  29. data/lib/rodauth/features/oauth_jwt_secured_authorization_request.rb +62 -0
  30. data/lib/rodauth/features/oauth_management_base.rb +2 -0
  31. data/lib/rodauth/features/oauth_pkce.rb +22 -26
  32. data/lib/rodauth/features/oauth_resource_indicators.rb +33 -21
  33. data/lib/rodauth/features/oauth_resource_server.rb +59 -0
  34. data/lib/rodauth/features/oauth_saml_bearer_grant.rb +5 -1
  35. data/lib/rodauth/features/oauth_token_introspection.rb +76 -46
  36. data/lib/rodauth/features/oauth_token_revocation.rb +46 -33
  37. data/lib/rodauth/features/oidc.rb +188 -95
  38. data/lib/rodauth/features/oidc_dynamic_client_registration.rb +89 -53
  39. data/lib/rodauth/oauth/database_extensions.rb +8 -6
  40. data/lib/rodauth/oauth/http_extensions.rb +61 -0
  41. data/lib/rodauth/oauth/railtie.rb +20 -0
  42. data/lib/rodauth/oauth/version.rb +1 -1
  43. data/lib/rodauth/oauth.rb +29 -1
  44. data/locales/en.yml +32 -22
  45. data/locales/pt.yml +32 -22
  46. data/templates/authorize.str +19 -24
  47. data/templates/device_search.str +1 -1
  48. data/templates/device_verification.str +2 -2
  49. data/templates/jwks_field.str +1 -0
  50. data/templates/new_oauth_application.str +1 -2
  51. data/templates/oauth_application.str +2 -2
  52. data/templates/oauth_application_oauth_grants.str +54 -0
  53. data/templates/oauth_applications.str +2 -2
  54. data/templates/oauth_grants.str +52 -0
  55. metadata +20 -16
  56. data/lib/generators/rodauth/oauth/templates/app/models/oauth_token.rb +0 -4
  57. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_tokens.html.erb +0 -39
  58. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_tokens.html.erb +0 -35
  59. data/lib/rodauth/features/oauth.rb +0 -9
  60. data/lib/rodauth/features/oauth_http_mac.rb +0 -86
  61. data/lib/rodauth/features/oauth_token_management.rb +0 -81
  62. data/lib/rodauth/oauth/refinements.rb +0 -48
  63. data/templates/jwt_public_key_field.str +0 -4
  64. data/templates/oauth_application_oauth_tokens.str +0 -52
  65. 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
- auth_value_method :oauth_applications_id_token_signed_response_alg_column, :id_token_signed_response_alg
69
- auth_value_method :oauth_applications_id_token_encrypted_response_alg_column, :id_token_encrypted_response_alg
70
- auth_value_method :oauth_applications_id_token_encrypted_response_enc_column, :id_token_encrypted_response_enc
71
- auth_value_method :oauth_applications_userinfo_signed_response_alg_column, :userinfo_signed_response_alg
72
- auth_value_method :oauth_applications_userinfo_encrypted_response_alg_column, :userinfo_encrypted_response_alg
73
- auth_value_method :oauth_applications_userinfo_encrypted_response_enc_column, :userinfo_encrypted_response_enc
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 :oauth_tokens_nonce_column, :nonce
78
- auth_value_method :oauth_tokens_acr_column, :acr
79
+ auth_value_method :oauth_grants_nonce_column, :nonce
80
+ auth_value_method :oauth_grants_acr_column, :acr
79
81
 
80
- translatable_method :invalid_scope_message, "The Access Token expired"
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
- auth_value_method :webfinger_relation, "http://openid.net/specs/connect/1.0/issuer"
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
- route(:userinfo) do |r|
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
- oauth_token = authorization_token
109
+ claims = authorization_token
107
110
 
108
- throw_json_response_error(authorization_required_error_status, "invalid_token") unless oauth_token
111
+ throw_json_response_error(oauth_authorization_required_error_status, "invalid_token") unless claims
109
112
 
110
- oauth_scopes = oauth_token["scope"].split(" ")
113
+ oauth_scopes = claims["scope"].split(" ")
111
114
 
112
- throw_json_response_error(authorization_required_error_status, "invalid_token") unless oauth_scopes.include?("openid")
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 => oauth_token["sub"]).first
117
+ account = db[accounts_table].where(account_id_column => claims["sub"]).first
115
118
 
116
- throw_json_response_error(authorization_required_error_status, "invalid_token") unless account
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" => oauth_token["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 => oauth_token["client_id"]).first
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(authorization_required_error_status, "invalid_token")
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
- route(:oidc_logout) do |r|
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
- oauth_token = jwt_decode(param("id_token_hint"), verify_claims: false)
169
- oauth_application_id = oauth_token["client_id"]
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 oauth_token["sub"] == jwt_subject(
173
- oauth_tokens_account_id_column => account_id,
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 oauth_token && oauth_token["iss"] == issuer
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 => oauth_token["client_id"]).first
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 openid_configuration(alt_issuer = nil)
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.get do
224
- json_response_success(openid_configuration_body(alt_issuer), cache: true)
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 webfinger
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
- json_payload = JSON.dump({
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 - last_account_login_at > max_age
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 create_oauth_token_from_authorization_code(oauth_grant, create_params, *)
391
- create_params[oauth_tokens_nonce_column] = oauth_grant[oauth_grants_nonce_column] if oauth_grant[oauth_grants_nonce_column]
392
- create_params[oauth_tokens_acr_column] = oauth_grant[oauth_grants_acr_column] if oauth_grant[oauth_grants_acr_column]
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(oauth_token)
404
- oauth_scopes = oauth_token[oauth_tokens_scopes_column].split(oauth_scope_separator)
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(oauth_token)
448
+ id_token_claims = jwt_claims(oauth_grant)
409
449
 
410
- id_token_claims[:nonce] = oauth_token[oauth_tokens_nonce_column] if oauth_token[oauth_tokens_nonce_column]
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] = oauth_token[oauth_tokens_acr_column] if oauth_token[oauth_tokens_acr_column]
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] = last_account_login_at.to_i
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 => oauth_token[oauth_tokens_account_id_column]).first
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: oauth_application[oauth_applications_id_token_signed_response_alg_column] || oauth_jwt_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
- oauth_token[:id_token] = jwt_encode(id_token_claims, **params)
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(oauth_token)
553
+ def json_access_token_payload(oauth_grant)
511
554
  payload = super
512
- payload["id_token"] = oauth_token[:id_token] if oauth_token[: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 do_authorize(response_params = {}, response_mode = param_or_nil("response_mode"))
529
- return super unless use_oauth_implicit_grant_type?
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 use_oauth_implicit_grant_type?
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
- create_params = {
553
- oauth_tokens_account_id_column => account_id,
554
- oauth_tokens_oauth_application_id_column => oauth_application[oauth_applications_id_column],
555
- oauth_tokens_scopes_column => scopes
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
- create_params[oauth_grants_nonce_column] = nonce
615
+ grant_params[oauth_grants_nonce_column] = nonce
559
616
  end
560
617
  if (acr = param_or_nil("acr"))
561
- create_params[oauth_grants_acr_column] = acr
618
+ grant_params[oauth_grants_acr_column] = acr
562
619
  end
563
- oauth_token = generate_oauth_token(create_params, false)
564
- generate_id_token(oauth_token)
565
- params = json_access_token_payload(oauth_token)
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 += ["none", "id_token", "code token", "code id_token", "id_token token", "code id_token token"]
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: [oauth_jwt_subject_type],
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: [oauth_jwt_jwe_algorithm].compact,
615
- id_token_encryption_enc_values_supported: [oauth_jwt_jwe_encryption_method].compact,
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