rodauth-oauth 1.1.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.
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rodauth/oauth"
4
+
5
+ module Rodauth
6
+ Feature.define(:oauth_pushed_authorization_request, :OauthJwtPushedAuthorizationRequest) do
7
+ depends :oauth_authorize_base
8
+
9
+ auth_value_method :oauth_require_pushed_authorization_requests, false
10
+ auth_value_method :oauth_applications_require_pushed_authorization_requests_column, :require_pushed_authorization_requests
11
+ auth_value_method :oauth_pushed_authorization_request_expires_in, 90 # 90 seconds
12
+ auth_value_method :oauth_require_pushed_authorization_request_iss_request_object, true
13
+
14
+ auth_value_method :oauth_pushed_authorization_requests_table, :oauth_pushed_requests
15
+
16
+ %i[
17
+ oauth_application_id params code expires_in
18
+ ].each do |column|
19
+ auth_value_method :"oauth_pushed_authorization_requests_#{column}_column", column
20
+ end
21
+
22
+ # /par
23
+ auth_server_route(:par) do |r|
24
+ require_oauth_application
25
+ before_par_route
26
+
27
+ r.post do
28
+ validate_par_params
29
+
30
+ ds = db[oauth_pushed_authorization_requests_table]
31
+
32
+ code = oauth_unique_id_generator
33
+ push_request_params = {
34
+ oauth_pushed_authorization_requests_oauth_application_id_column => oauth_application[oauth_applications_id_column],
35
+ oauth_pushed_authorization_requests_code_column => code,
36
+ oauth_pushed_authorization_requests_params_column => URI.encode_www_form(request.params),
37
+ oauth_pushed_authorization_requests_expires_in_column => Sequel.date_add(Sequel::CURRENT_TIMESTAMP,
38
+ seconds: oauth_pushed_authorization_request_expires_in)
39
+ }
40
+
41
+ rescue_from_uniqueness_error do
42
+ ds.insert(push_request_params)
43
+ end
44
+
45
+ json_response_success(
46
+ "request_uri" => "urn:ietf:params:oauth:request_uri:#{code}",
47
+ "expires_in" => oauth_pushed_authorization_request_expires_in
48
+ )
49
+ end
50
+ end
51
+
52
+ def check_csrf?
53
+ case request.path
54
+ when par_path
55
+ false
56
+ else
57
+ super
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def validate_par_params
64
+ # https://datatracker.ietf.org/doc/html/rfc9126#section-2.1
65
+ # The request_uri authorization request parameter is one exception, and it MUST NOT be provided.
66
+ redirect_response_error("invalid_request") if param_or_nil("request_uri")
67
+
68
+ if (request_object = param_or_nil("request")) && features.include?(:oauth_jwt_secured_authorization_request)
69
+ claims = decode_request_object(request_object)
70
+
71
+ # https://datatracker.ietf.org/doc/html/rfc9126#section-3-5.3
72
+ # reject the request if the authenticated client_id does not match the client_id claim in the Request Object
73
+ if (client_id = claims["client_id"]) && (client_id != oauth_application[oauth_applications_client_id_column])
74
+ redirect_response_error("invalid_request_object")
75
+ end
76
+
77
+ # requiring the iss claim to match the client_id is at the discretion of the authorization server
78
+ if oauth_require_pushed_authorization_request_iss_request_object &&
79
+ (iss = claims.delete("iss")) &&
80
+ iss != oauth_application[oauth_applications_client_id_column]
81
+ redirect_response_error("invalid_request_object")
82
+ end
83
+
84
+ if (aud = claims.delete("aud")) && !verify_aud(aud, oauth_jwt_issuer)
85
+ redirect_response_error("invalid_request_object")
86
+ end
87
+
88
+ claims.delete("exp")
89
+ request.params.delete("request")
90
+
91
+ claims.each do |k, v|
92
+ request.params[k.to_s] = v
93
+ end
94
+ end
95
+
96
+ validate_authorize_params
97
+ end
98
+
99
+ def validate_authorize_params
100
+ return super unless request.get? && request.path == authorize_path
101
+
102
+ if (request_uri = param_or_nil("request_uri"))
103
+ code = request_uri.delete_prefix("urn:ietf:params:oauth:request_uri:")
104
+
105
+ table = oauth_pushed_authorization_requests_table
106
+ ds = db[table]
107
+
108
+ pushed_request = ds.where(
109
+ oauth_pushed_authorization_requests_oauth_application_id_column => oauth_application[oauth_applications_id_column],
110
+ oauth_pushed_authorization_requests_code_column => code
111
+ ).where(
112
+ Sequel.expr(Sequel[table][oauth_pushed_authorization_requests_expires_in_column]) >= Sequel::CURRENT_TIMESTAMP
113
+ ).first
114
+
115
+ redirect_response_error("invalid_request") unless pushed_request
116
+
117
+ URI.decode_www_form(pushed_request[oauth_pushed_authorization_requests_params_column]).each do |k, v|
118
+ request.params[k.to_s] = v
119
+ end
120
+
121
+ elsif oauth_require_pushed_authorization_requests ||
122
+ (oauth_application && oauth_application[oauth_applications_require_pushed_authorization_requests_column])
123
+ redirect_authorize_error("request_uri")
124
+ end
125
+ super
126
+ end
127
+
128
+ def oauth_server_metadata_body(*)
129
+ super.tap do |data|
130
+ data[:require_pushed_authorization_requests] = oauth_require_pushed_authorization_requests
131
+ data[:pushed_authorization_request_endpoint] = par_url
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+ require "ipaddr"
5
+ require "uri"
6
+ require "rodauth/oauth"
7
+
8
+ module Rodauth
9
+ Feature.define(:oauth_tls_client_auth, :OauthTlsClientAuth) do
10
+ depends :oauth_jwt_base
11
+
12
+ auth_value_method :oauth_tls_client_certificate_bound_access_tokens, false
13
+
14
+ %i[
15
+ tls_client_auth_subject_dn tls_client_auth_san_dns
16
+ tls_client_auth_san_uri tls_client_auth_san_ip
17
+ tls_client_auth_san_email tls_client_certificate_bound_access_tokens
18
+ ].each do |column|
19
+ auth_value_method :"oauth_applications_#{column}_column", column
20
+ end
21
+
22
+ auth_value_method :oauth_grants_certificate_thumbprint_column, :certificate_thumbprint
23
+
24
+ def oauth_token_endpoint_auth_methods_supported
25
+ super | %w[tls_client_auth self_signed_tls_client_auth]
26
+ end
27
+
28
+ private
29
+
30
+ def validate_token_params
31
+ # For all requests to the authorization server utilizing mutual-TLS client authentication,
32
+ # the client MUST include the client_id parameter
33
+ redirect_response_error("invalid_request") if client_certificate && !param_or_nil("client_id")
34
+
35
+ super
36
+ end
37
+
38
+ def require_oauth_application
39
+ return super unless client_certificate
40
+
41
+ authorization_required unless oauth_application
42
+
43
+ if supports_auth_method?(oauth_application, "tls_client_auth")
44
+ # It relies on a validated certificate chain [RFC5280]
45
+
46
+ ssl_verify = request.env["SSL_CLIENT_VERIFY"] || request.env["HTTP_SSL_CLIENT_VERIFY"] || request.env["HTTP_X_SSL_CLIENT_VERIFY"]
47
+
48
+ authorization_required unless ssl_verify == "SUCCESS"
49
+
50
+ # and a single subject distinguished name (DN) or a single subject alternative name (SAN) to
51
+ # authenticate the client. Only one subject name value of any type is used for each client.
52
+
53
+ name_matches = if oauth_application[:tls_client_auth_subject_dn]
54
+ distinguished_name_match?(client_certificate.subject, oauth_application[:tls_client_auth_subject_dn])
55
+ elsif (dns = oauth_application[:tls_client_auth_san_dns])
56
+ client_certificate_sans.any? { |san| san.tag == 2 && OpenSSL::SSL.verify_hostname(dns, san.value) }
57
+ elsif (uri = oauth_application[:tls_client_auth_san_uri])
58
+ uri = URI(uri)
59
+ client_certificate_sans.any? { |san| san.tag == 6 && URI(san.value) == uri }
60
+ elsif (ip = oauth_application[:tls_client_auth_san_ip])
61
+ ip = IPAddr.new(ip).hton
62
+ client_certificate_sans.any? { |san| san.tag == 7 && san.value == ip }
63
+ elsif (email = oauth_application[:tls_client_auth_san_email])
64
+ client_certificate_sans.any? { |san| san.tag == 1 && san.value == email }
65
+ else
66
+ false
67
+ end
68
+ authorization_required unless name_matches
69
+
70
+ oauth_application
71
+ elsif supports_auth_method?(oauth_application, "self_signed_tls_client_auth")
72
+ jwks = oauth_application_jwks(oauth_application)
73
+
74
+ thumbprint = jwk_thumbprint(key_to_jwk(client_certificate.public_key))
75
+
76
+ # The client is successfully authenticated if the certificate that it presented during the handshake
77
+ # matches one of the certificates configured or registered for that particular client.
78
+ authorization_required unless jwks.any? { |jwk| Array(jwk[:x5c]).first == thumbprint }
79
+
80
+ oauth_application
81
+ else
82
+ super
83
+ end
84
+ rescue URI::InvalidURIError, IPAddr::InvalidAddressError
85
+ authorization_required
86
+ end
87
+
88
+ def store_token(grant_params, update_params = {})
89
+ return super unless client_certificate && (
90
+ oauth_tls_client_certificate_bound_access_tokens ||
91
+ oauth_application[oauth_applications_tls_client_certificate_bound_access_tokens_column]
92
+ )
93
+
94
+ update_params[oauth_grants_certificate_thumbprint_column] = jwk_thumbprint(key_to_jwk(client_certificate.public_key))
95
+ super
96
+ end
97
+
98
+ def jwt_claims(oauth_grant)
99
+ claims = super
100
+
101
+ return claims unless oauth_grant[oauth_grants_certificate_thumbprint_column]
102
+
103
+ claims[:cnf] = {
104
+ "x5t#S256" => oauth_grant[oauth_grants_certificate_thumbprint_column]
105
+ }
106
+
107
+ claims
108
+ end
109
+
110
+ def json_token_introspect_payload(grant_or_claims)
111
+ claims = super
112
+
113
+ return claims unless grant_or_claims && grant_or_claims[oauth_grants_certificate_thumbprint_column]
114
+
115
+ claims[:cnf] = {
116
+ "x5t#S256" => grant_or_claims[oauth_grants_certificate_thumbprint_column]
117
+ }
118
+
119
+ claims
120
+ end
121
+
122
+ def oauth_server_metadata_body(*)
123
+ super.tap do |data|
124
+ data[:tls_client_certificate_bound_access_tokens] = oauth_tls_client_certificate_bound_access_tokens
125
+ end
126
+ end
127
+
128
+ def client_certificate
129
+ return @client_certificate if defined?(@client_certificate)
130
+
131
+ unless (pem_cert = request.env["SSL_CLIENT_CERT"] || request.env["HTTP_SSL_CLIENT_CERT"] || request.env["HTTP_X_SSL_CLIENT_CERT"])
132
+ return
133
+ end
134
+
135
+ return if pem_cert.empty?
136
+
137
+ @certificate = OpenSSL::X509::Certificate.new(pem_cert)
138
+ end
139
+
140
+ def client_certificate_sans
141
+ return @client_certificate_sans if defined?(@client_certificate_sans)
142
+
143
+ @client_certificate_sans = begin
144
+ return [] unless client_certificate
145
+
146
+ san = client_certificate.extensions.find { |ext| ext.oid == "subjectAltName" }
147
+
148
+ return [] unless san
149
+
150
+ ostr = OpenSSL::ASN1.decode(san.to_der).value.last
151
+
152
+ sans = OpenSSL::ASN1.decode(ostr.value)
153
+
154
+ return [] unless sans
155
+
156
+ sans.value
157
+ end
158
+ end
159
+
160
+ def distinguished_name_match?(sub1, sub2)
161
+ sub1 = OpenSSL::X509::Name.parse(sub1) if sub1.is_a?(String)
162
+ sub2 = OpenSSL::X509::Name.parse(sub2) if sub2.is_a?(String)
163
+ # OpenSSL::X509::Name#cp calls X509_NAME_cmp via openssl.
164
+ # https://www.openssl.org/docs/manmaster/man3/X509_NAME_cmp.html
165
+ # This procedure adheres to the matching rules for Distinguished Names (DN) given in
166
+ # RFC 4517 section 4.2.15 and RFC 5280 section 7.1.
167
+ sub1.cmp(sub2).zero?
168
+ end
169
+ end
170
+ end
@@ -63,12 +63,12 @@ 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
 
70
70
  %i[
71
- subject_type application_type sector_identifier_uri
71
+ subject_type application_type sector_identifier_uri initiate_login_uri
72
72
  id_token_signed_response_alg id_token_encrypted_response_alg id_token_encrypted_response_enc
73
73
  userinfo_signed_response_alg userinfo_encrypted_response_alg userinfo_encrypted_response_enc
74
74
  ].each do |column|
@@ -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,
@@ -112,7 +119,7 @@ module Rodauth
112
119
 
113
120
  throw_json_response_error(oauth_authorization_required_error_status, "invalid_token") unless oauth_scopes.include?("openid")
114
121
 
115
- account = db[accounts_table].where(account_id_column => claims["sub"]).first
122
+ account = account_ds(claims["sub"]).first
116
123
 
117
124
  throw_json_response_error(oauth_authorization_required_error_status, "invalid_token") unless account
118
125
 
@@ -126,7 +133,7 @@ module Rodauth
126
133
 
127
134
  oauth_grant = valid_oauth_grant_ds(
128
135
  oauth_grants_oauth_application_id_column => @oauth_application[oauth_applications_id_column],
129
- oauth_grants_account_id_column => account[account_id_column]
136
+ **resource_owner_params_from_jwt_claims(claims)
130
137
  ).first
131
138
 
132
139
  claims_locales = oauth_grant[oauth_grants_claims_locales_column] if oauth_grant
@@ -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
- if sc && sc.include?("offline_access")
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
- return unless (response_type = param_or_nil("response_type"))
297
- return unless response_type.include?("id_token")
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 param_or_nil("nonce")
334
+ redirect_response_error("invalid_request") unless response_mode.nil? || response_mode == "fragment"
300
335
  end
301
336
 
302
337
  def require_authorizable_account
@@ -333,8 +368,9 @@ module Rodauth
333
368
 
334
369
  identifier_uri = URI(identifier_uri).host
335
370
 
336
- account_id = oauth_grant[oauth_grants_account_id_column]
337
- Digest::SHA256.hexdigest("#{identifier_uri}#{account_id}#{oauth_jwt_subject_secret}")
371
+ account_ids = oauth_grant.values_at(oauth_grants_resource_owner_columns)
372
+ values = [identifier_uri, *account_ids, oauth_jwt_subject_secret]
373
+ Digest::SHA256.hexdigest(values.join)
338
374
  else
339
375
  raise StandardError, "unexpected subject (#{subject_type})"
340
376
  end
@@ -434,8 +470,8 @@ module Rodauth
434
470
  end
435
471
 
436
472
  def create_oauth_grant_with_token(create_params = {})
473
+ create_params.merge!(resource_owner_params)
437
474
  create_params[oauth_grants_type_column] = "hybrid"
438
- create_params[oauth_grants_account_id_column] = account_id
439
475
  create_params[oauth_grants_expires_in_column] = Sequel.date_add(Sequel::CURRENT_TIMESTAMP, seconds: oauth_access_token_expires_in)
440
476
  authorization_code = create_oauth_grant(create_params)
441
477
  access_token = if oauth_jwt_access_tokens
@@ -462,24 +498,7 @@ module Rodauth
462
498
  signing_algorithm = oauth_application[oauth_applications_id_token_signed_response_alg_column] ||
463
499
  oauth_jwt_keys.keys.first
464
500
 
465
- id_token_claims = jwt_claims(oauth_grant)
466
-
467
- id_token_claims[:nonce] = oauth_grant[oauth_grants_nonce_column] if oauth_grant[oauth_grants_nonce_column]
468
-
469
- id_token_claims[:acr] = oauth_grant[oauth_grants_acr_column] if oauth_grant[oauth_grants_acr_column]
470
-
471
- # Time when the End-User authentication occurred.
472
- id_token_claims[:auth_time] = get_oidc_account_last_login_at(oauth_grant[oauth_grants_account_id_column]).to_i
473
-
474
- # Access Token hash value.
475
- if (access_token = oauth_grant[oauth_grants_token_column])
476
- id_token_claims[:at_hash] = id_token_hash(access_token, signing_algorithm)
477
- end
478
-
479
- # code hash value.
480
- if (code = oauth_grant[oauth_grants_code_column])
481
- id_token_claims[:c_hash] = id_token_hash(code, signing_algorithm)
482
- end
501
+ id_claims = id_token_claims(oauth_grant, signing_algorithm)
483
502
 
484
503
  account = db[accounts_table].where(account_id_column => oauth_grant[oauth_grants_account_id_column]).first
485
504
 
@@ -499,7 +518,7 @@ module Rodauth
499
518
 
500
519
  # 5.4 - However, when no Access Token is issued (which is the case for the response_type value id_token),
501
520
  # the resulting Claims are returned in the ID Token.
502
- fill_with_account_claims(id_token_claims, account, oauth_scopes, param_or_nil("claims_locales")) if include_claims
521
+ fill_with_account_claims(id_claims, account, oauth_scopes, param_or_nil("claims_locales")) if include_claims
503
522
 
504
523
  params = {
505
524
  jwks: oauth_application_jwks(oauth_application),
@@ -508,7 +527,30 @@ module Rodauth
508
527
  encryption_method: oauth_application[oauth_applications_id_token_encrypted_response_enc_column]
509
528
  }.compact
510
529
 
511
- oauth_grant[:id_token] = jwt_encode(id_token_claims, **params)
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
512
554
  end
513
555
 
514
556
  # aka fill_with_standard_claims
@@ -587,14 +629,14 @@ module Rodauth
587
629
  additional_info = additional_claims_info[param] || EMPTY_HASH
588
630
  value = additional_info["value"] || meth[account, param]
589
631
  value = nil if additional_info["values"] && additional_info["values"].include?(value)
590
- cl[param] = value if value
632
+ cl[param] = value unless value.nil?
591
633
  end
592
634
  elsif claims_locales.nil?
593
635
  lambda do |account, param, cl = claims|
594
636
  additional_info = additional_claims_info[param] || EMPTY_HASH
595
637
  value = additional_info["value"] || meth[account, param, nil]
596
638
  value = nil if additional_info["values"] && additional_info["values"].include?(value)
597
- cl[param] = value if value
639
+ cl[param] = value unless value.nil?
598
640
  end
599
641
  else
600
642
  lambda do |account, param, cl = claims|
@@ -626,10 +668,9 @@ module Rodauth
626
668
 
627
669
  def check_valid_response_type?
628
670
  case param_or_nil("response_type")
629
- 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"
630
673
  true
631
- when "code token", "id_token token", "code id_token token"
632
- supports_token_response_type?
633
674
  else
634
675
  super
635
676
  end
@@ -641,10 +682,6 @@ module Rodauth
641
682
  param("response_type") == "none"
642
683
  end
643
684
 
644
- def supports_token_response_type?
645
- features.include?(:oauth_implicit_grant)
646
- end
647
-
648
685
  def do_authorize(response_params = {}, response_mode = param_or_nil("response_mode"))
649
686
  response_type = param("response_type")
650
687
  case response_type
@@ -653,8 +690,6 @@ module Rodauth
653
690
  generate_id_token(grant_params, true)
654
691
  response_params.replace("id_token" => grant_params[:id_token])
655
692
  when "code token"
656
- redirect_response_error("invalid_request") unless supports_token_response_type?
657
-
658
693
  response_params.replace(create_oauth_grant_with_token)
659
694
  when "code id_token"
660
695
  params = _do_authorize_code
@@ -665,16 +700,12 @@ module Rodauth
665
700
  "code" => params["code"]
666
701
  )
667
702
  when "id_token token"
668
- redirect_response_error("invalid_request") unless supports_token_response_type?
669
-
670
703
  grant_params = oidc_grant_params.merge(oauth_grants_type_column => "hybrid")
671
704
  oauth_grant = _do_authorize_token(grant_params)
672
705
  generate_id_token(oauth_grant)
673
706
 
674
707
  response_params.replace(json_access_token_payload(oauth_grant))
675
708
  when "code id_token token"
676
- redirect_response_error("invalid_request") unless supports_token_response_type?
677
-
678
709
  params = create_oauth_grant_with_token
679
710
  oauth_grant = valid_oauth_grant_ds.where(oauth_grants_code_column => params["code"]).first
680
711
  oauth_grant[oauth_grants_token_column] = params["access_token"]
@@ -691,9 +722,10 @@ module Rodauth
691
722
 
692
723
  def oidc_grant_params
693
724
  grant_params = {
694
- oauth_grants_account_id_column => account_id,
725
+ **resource_owner_params,
695
726
  oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
696
- 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")
697
729
  }
698
730
  if (nonce = param_or_nil("nonce"))
699
731
  grant_params[oauth_grants_nonce_column] = nonce
@@ -708,6 +740,12 @@ module Rodauth
708
740
  grant_params
709
741
  end
710
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
+
711
749
  def authorize_response(params, mode)
712
750
  redirect_url = URI.parse(redirect_uri)
713
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])
@@ -43,7 +43,11 @@ module Rodauth
43
43
  end
44
44
  end
45
45
 
46
- if (value = @oauth_application_params[oauth_applications_sector_identifier_uri_column])
46
+ if (value = @oauth_application_params[oauth_applications_sector_identifier_uri_column]) && !check_valid_uri?(value)
47
+ register_throw_json_response_error("invalid_redirect_uri", register_invalid_uri_message(value))
48
+ end
49
+
50
+ if (value = @oauth_application_params[oauth_applications_initiate_login_uri_column])
47
51
  uri = URI(value)
48
52
 
49
53
  unless uri.scheme == "https" || uri.host == "localhost"
@@ -170,6 +174,44 @@ module Rodauth
170
174
  register_throw_json_response_error("invalid_client_metadata",
171
175
  register_invalid_client_metadata_message("userinfo_encrypted_response_enc", value))
172
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
173
215
  end
174
216
 
175
217
  def validate_client_registration_response_type(response_type, grant_types)
@@ -219,5 +261,13 @@ module Rodauth
219
261
  def register_invalid_application_type_message(application_type)
220
262
  "The application type '#{application_type}' is not allowed."
221
263
  end
264
+
265
+ def initialize_register_params(create_params, return_params)
266
+ super
267
+ registration_access_token = oauth_unique_id_generator
268
+ create_params[oauth_applications_registration_access_token_column] = secret_hash(registration_access_token)
269
+ return_params["registration_access_token"] = registration_access_token
270
+ return_params["registration_client_uri"] = "#{base_url}/#{registration_client_uri_route}/#{return_params['client_id']}"
271
+ end
222
272
  end
223
273
  end