rodauth-oauth 0.10.4 → 1.0.0.pre.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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