From 79b702c4308beced5f8433c2f6016e1af33ce35b Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 14 Apr 2026 10:32:33 +0200 Subject: [PATCH 1/9] update the local patch --- lib/omniauth/strategies/openid_connect.rb | 63 ++++++++++++++- .../strategies/openid_connect_test.rb | 76 +++++++++++++++++++ 2 files changed, 138 insertions(+), 1 deletion(-) diff --git a/lib/omniauth/strategies/openid_connect.rb b/lib/omniauth/strategies/openid_connect.rb index 73dd0fe0..7d404ead 100644 --- a/lib/omniauth/strategies/openid_connect.rb +++ b/lib/omniauth/strategies/openid_connect.rb @@ -110,7 +110,8 @@ def client end def config - @config ||= ::OpenIDConnect::Discovery::Provider::Config.discover!(options.issuer) + configure_discovery_scheme + @config ||= ::OpenIDConnect::Discovery::Provider::Config.discover!(discovery_base_url) end def request_phase @@ -250,6 +251,63 @@ def issuer ::OpenIDConnect::Discovery::Provider.discover!(resource).issuer end + # When discovery is enabled we need a *stable* base URL. + # + # If the user provides an issuer without an explicit scheme (e.g. "keycloak:8080/realms/foo"), + # passing it to discovery can lead to unintended HTTPS/TLS attempts depending on underlying client defaults. + # + # Recommended behavior: only trust options.issuer for discovery if it is an absolute URI with http/https scheme. + # Otherwise, fall back to client_options.scheme/host/port. + def discovery_base_url + issuer = options.issuer.to_s + if issuer.match?(/\Ahttps?:\/\//) + uri = URI.parse(issuer) + # If path is empty or just '/', use issuer as-is (it's already a base URL) + if uri.path.nil? || uri.path.empty? || uri.path == '/' + issuer + else + # Has a real path beyond '/', extract base URL + resource = "#{uri.scheme}://#{uri.host}" + default_port = (uri.scheme == 'https') ? 443 : 80 + resource = "#{resource}:#{uri.port}" if uri.port != default_port + resource + end + else + resource = "#{client_options.scheme}://#{client_options.host}" + resource = "#{resource}:#{client_options.port}" if client_options.port + resource + end + end + + # Configure the SWD (Simple Web Discovery) library to use the correct URI scheme. + # + # The SWD library (used by openid_connect gem for discovery) defaults to URI::HTTPS, + # which causes SSL connection attempts even when an HTTP URL is explicitly provided. + # This results in "SSL_connect" errors when connecting to non-SSL servers (e.g., + # development Keycloak instances running on HTTP). + # + # This method fixes the issue by setting SWD.url_builder to URI::HTTP or URI::HTTPS + # based on the actual scheme of the discovery_base_url, ensuring the gem respects + # the explicitly configured protocol. + # + # See: https://github.com/nov/openid_connect/issues/47 + def configure_discovery_scheme + base_url = discovery_base_url + return if base_url.nil? || base_url.empty? + + uri = URI.parse(base_url) + + # Set SWD.url_builder to use HTTP or HTTPS based on the discovery URL scheme + # This prevents the gem from forcing HTTPS when HTTP is explicitly specified + if uri.scheme == 'http' + require 'swd' + SWD.url_builder = URI::HTTP + elsif uri.scheme == 'https' + require 'swd' + SWD.url_builder = URI::HTTPS + end + end + def discover! return unless options.discovery @@ -258,6 +316,9 @@ def discover! client_options.userinfo_endpoint = config.userinfo_endpoint client_options.jwks_uri = config.jwks_uri client_options.end_session_endpoint = config.end_session_endpoint if config.respond_to?(:end_session_endpoint) + + # Keep issuer consistent with what the provider advertises so later token verification uses the correct issuer. + options.issuer = config.issuer if config.respond_to?(:issuer) && config.issuer end def user_info diff --git a/test/lib/omniauth/strategies/openid_connect_test.rb b/test/lib/omniauth/strategies/openid_connect_test.rb index dfc519ee..d8f74bca 100644 --- a/test/lib/omniauth/strategies/openid_connect_test.rb +++ b/test/lib/omniauth/strategies/openid_connect_test.rb @@ -127,6 +127,82 @@ def test_request_phase_with_discovery assert_nil strategy.options.client_options.end_session_endpoint end + def test_request_phase_with_discovery_http_scheme_and_port + expected_redirect = %r{^http://keycloak:8080/authorize\?client_id=1234&nonce=\w{32}&response_type=code&scope=openid&state=\w{32}$} + + strategy.options.client_options.scheme = 'http' + strategy.options.client_options.host = 'keycloak' + strategy.options.client_options.port = 8080 + strategy.options.discovery = true + + # Simulate provider discovery for issuer (used when options.issuer is empty) + issuer = stub('OpenIDConnect::Discovery::Issuer') + issuer.stubs(:issuer).returns('http://keycloak:8080/realms/quepid') + ::OpenIDConnect::Discovery::Provider.stubs(:discover!).with('http://keycloak:8080').returns(issuer) + + config = stub('OpenIDConnect::Discovery::Provider::Config') + config.stubs(:issuer).returns('http://keycloak:8080/realms/quepid') + config.stubs(:authorization_endpoint).returns('http://keycloak:8080/authorize') + config.stubs(:token_endpoint).returns('http://keycloak:8080/token') + config.stubs(:userinfo_endpoint).returns('http://keycloak:8080/userinfo') + config.stubs(:jwks_uri).returns('http://keycloak:8080/jwks') + ::OpenIDConnect::Discovery::Provider::Config.stubs(:discover!).with('http://keycloak:8080').returns(config) + + strategy.expects(:redirect).with(regexp_matches(expected_redirect)) + strategy.request_phase + + assert_equal 'http://keycloak:8080/realms/quepid', strategy.options.issuer + assert_equal 'http://keycloak:8080/authorize', strategy.options.client_options.authorization_endpoint + end + + def test_discovery_ignores_scheme_less_issuer_and_falls_back_to_client_options + # issuer is present but scheme-less; discovery should not use it as base + strategy.options.issuer = 'keycloak:8080/realms/quepid' + strategy.options.discovery = true + strategy.options.client_options.scheme = 'http' + strategy.options.client_options.host = 'keycloak' + strategy.options.client_options.port = 8080 + + config = stub('OpenIDConnect::Discovery::Provider::Config') + config.stubs(:issuer).returns('http://keycloak:8080/realms/quepid') + config.stubs(:authorization_endpoint).returns('http://keycloak:8080/authorize') + config.stubs(:token_endpoint).returns('http://keycloak:8080/token') + config.stubs(:userinfo_endpoint).returns('http://keycloak:8080/userinfo') + config.stubs(:jwks_uri).returns('http://keycloak:8080/jwks') + + # key assertion: discover! uses http://keycloak:8080 (from client_options), NOT the scheme-less issuer + ::OpenIDConnect::Discovery::Provider::Config.expects(:discover!).with('http://keycloak:8080').returns(config) + + # call the private method via request_phase which triggers discover! + ::OpenIDConnect::Discovery::Provider.stubs(:discover!).returns(stub('OpenIDConnect::Discovery::Issuer', issuer: 'http://keycloak:8080/realms/quepid')) + + strategy.expects(:redirect) + strategy.request_phase + + assert_equal 'http://keycloak:8080/realms/quepid', strategy.options.issuer + end + + def test_configure_discovery_scheme_sets_swd_url_builder_for_http + require 'swd' + + # Test with HTTP scheme + strategy.options.issuer = 'http://keycloak:8080/realms/quepid' + strategy.options.discovery = true + + # Call the private method to configure discovery scheme + strategy.send(:configure_discovery_scheme) + + # Verify SWD.url_builder is set to URI::HTTP for HTTP URLs + assert_equal URI::HTTP, SWD.url_builder + + # Test with HTTPS scheme + strategy.options.issuer = 'https://example.com/realms/test' + strategy.send(:configure_discovery_scheme) + + # Verify SWD.url_builder is set to URI::HTTPS for HTTPS URLs + assert_equal URI::HTTPS, SWD.url_builder + end + def test_request_phase_with_response_mode expected_redirect = %r{^https://example\.com/authorize\?client_id=1234&nonce=\w{32}&response_mode=form_post&response_type=id_token&scope=openid&state=\w{32}$} strategy.options.issuer = 'example.com' From e6e550b04cd2cc752d83f7f939cd725fe8ee7809 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 14 Apr 2026 10:49:18 +0200 Subject: [PATCH 2/9] Retest with nested realm from keycloak --- lib/omniauth/strategies/openid_connect.rb | 15 ++++----------- .../omniauth/strategies/openid_connect_test.rb | 2 +- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/lib/omniauth/strategies/openid_connect.rb b/lib/omniauth/strategies/openid_connect.rb index 7d404ead..7bf7ca9c 100644 --- a/lib/omniauth/strategies/openid_connect.rb +++ b/lib/omniauth/strategies/openid_connect.rb @@ -261,18 +261,11 @@ def issuer def discovery_base_url issuer = options.issuer.to_s if issuer.match?(/\Ahttps?:\/\//) - uri = URI.parse(issuer) - # If path is empty or just '/', use issuer as-is (it's already a base URL) - if uri.path.nil? || uri.path.empty? || uri.path == '/' - issuer - else - # Has a real path beyond '/', extract base URL - resource = "#{uri.scheme}://#{uri.host}" - default_port = (uri.scheme == 'https') ? 443 : 80 - resource = "#{resource}:#{uri.port}" if uri.port != default_port - resource - end + # Use the full issuer URL as-is for discovery + # The discovery endpoint will be: issuer + '/.well-known/openid-configuration' + issuer else + # Issuer doesn't have a scheme, construct URL from client_options resource = "#{client_options.scheme}://#{client_options.host}" resource = "#{resource}:#{client_options.port}" if client_options.port resource diff --git a/test/lib/omniauth/strategies/openid_connect_test.rb b/test/lib/omniauth/strategies/openid_connect_test.rb index d8f74bca..7dbb1881 100644 --- a/test/lib/omniauth/strategies/openid_connect_test.rb +++ b/test/lib/omniauth/strategies/openid_connect_test.rb @@ -146,7 +146,7 @@ def test_request_phase_with_discovery_http_scheme_and_port config.stubs(:token_endpoint).returns('http://keycloak:8080/token') config.stubs(:userinfo_endpoint).returns('http://keycloak:8080/userinfo') config.stubs(:jwks_uri).returns('http://keycloak:8080/jwks') - ::OpenIDConnect::Discovery::Provider::Config.stubs(:discover!).with('http://keycloak:8080').returns(config) + ::OpenIDConnect::Discovery::Provider::Config.stubs(:discover!).with('http://keycloak:8080/realms/quepid').returns(config) strategy.expects(:redirect).with(regexp_matches(expected_redirect)) strategy.request_phase From 847aad274fd53542f25e9ce9e3b4d289b6fd54d1 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 14 Apr 2026 14:22:10 +0200 Subject: [PATCH 3/9] add debugging --- lib/omniauth/strategies/openid_connect.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/omniauth/strategies/openid_connect.rb b/lib/omniauth/strategies/openid_connect.rb index 7bf7ca9c..45316fba 100644 --- a/lib/omniauth/strategies/openid_connect.rb +++ b/lib/omniauth/strategies/openid_connect.rb @@ -260,14 +260,27 @@ def issuer # Otherwise, fall back to client_options.scheme/host/port. def discovery_base_url issuer = options.issuer.to_s + + # Debug logging to see what we're working with + if defined?(Rails) && Rails.logger + Rails.logger.debug "[OpenIDConnect] discovery_base_url - options.issuer: #{options.issuer.inspect}" + Rails.logger.debug "[OpenIDConnect] discovery_base_url - issuer string: #{issuer}" + end + if issuer.match?(/\Ahttps?:\/\//) # Use the full issuer URL as-is for discovery # The discovery endpoint will be: issuer + '/.well-known/openid-configuration' + if defined?(Rails) && Rails.logger + Rails.logger.debug "[OpenIDConnect] discovery_base_url - using issuer as-is: #{issuer}" + end issuer else # Issuer doesn't have a scheme, construct URL from client_options resource = "#{client_options.scheme}://#{client_options.host}" resource = "#{resource}:#{client_options.port}" if client_options.port + if defined?(Rails) && Rails.logger + Rails.logger.debug "[OpenIDConnect] discovery_base_url - constructed from client_options: #{resource}" + end resource end end From 20857a0cccbc2e297cd1f2d26a4ebe0d65a3ac80 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 14 Apr 2026 14:33:02 +0200 Subject: [PATCH 4/9] more logging --- lib/omniauth/strategies/openid_connect.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/omniauth/strategies/openid_connect.rb b/lib/omniauth/strategies/openid_connect.rb index 45316fba..4c64d7d9 100644 --- a/lib/omniauth/strategies/openid_connect.rb +++ b/lib/omniauth/strategies/openid_connect.rb @@ -115,7 +115,18 @@ def config end def request_phase + # Debug logging to diagnose issuer issues + if defined?(Rails) && Rails.logger + Rails.logger.debug "[OpenIDConnect] request_phase - options.issuer BEFORE: #{options.issuer.inspect}" + Rails.logger.debug "[OpenIDConnect] request_phase - options.issuer.to_s.empty?: #{options.issuer.to_s.empty?}" + end + options.issuer = issuer if options.issuer.to_s.empty? + + if defined?(Rails) && Rails.logger + Rails.logger.debug "[OpenIDConnect] request_phase - options.issuer AFTER: #{options.issuer.inspect}" + end + discover! redirect authorize_uri end From a7f7458b0978e49365321e928e63dd2b0b942415 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 14 Apr 2026 15:21:31 +0200 Subject: [PATCH 5/9] Full cycle works! --- lib/omniauth/strategies/openid_connect.rb | 39 ++++++++++++++++------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/lib/omniauth/strategies/openid_connect.rb b/lib/omniauth/strategies/openid_connect.rb index 4c64d7d9..1e499760 100644 --- a/lib/omniauth/strategies/openid_connect.rb +++ b/lib/omniauth/strategies/openid_connect.rb @@ -115,16 +115,17 @@ def config end def request_phase + puts "\nHERE I AM\n" # Debug logging to diagnose issuer issues if defined?(Rails) && Rails.logger - Rails.logger.debug "[OpenIDConnect] request_phase - options.issuer BEFORE: #{options.issuer.inspect}" - Rails.logger.debug "[OpenIDConnect] request_phase - options.issuer.to_s.empty?: #{options.issuer.to_s.empty?}" + puts "[OpenIDConnect] request_phase - options.issuer BEFORE: #{options.issuer.inspect}" + puts "[OpenIDConnect] request_phase - options.issuer.to_s.empty?: #{options.issuer.to_s.empty?}" end - options.issuer = issuer if options.issuer.to_s.empty? + options.issuer = issuer if issuer_empty? if defined?(Rails) && Rails.logger - Rails.logger.debug "[OpenIDConnect] request_phase - options.issuer AFTER: #{options.issuer.inspect}" + puts "[OpenIDConnect] request_phase - options.issuer AFTER: #{options.issuer.inspect}" end discover! @@ -146,7 +147,7 @@ def callback_phase return unless valid_response_type? - options.issuer = issuer if options.issuer.nil? || options.issuer.empty? + options.issuer = issuer if issuer_empty? verify_id_token!(params['id_token']) if configured_response_type == 'id_token' discover! @@ -169,7 +170,7 @@ def callback_phase def other_phase if logout_path_pattern.match?(current_path) - options.issuer = issuer if options.issuer.to_s.empty? + options.issuer = issuer if issuer_empty? discover! return redirect(end_session_uri) if end_session_uri end @@ -262,6 +263,15 @@ def issuer ::OpenIDConnect::Discovery::Provider.discover!(resource).issuer end + # Helper method to safely check if issuer is empty, handling Proc/lambda values + def issuer_empty? + return true if options.issuer.nil? + + issuer_value = options.issuer + issuer_value = issuer_value.call if issuer_value.respond_to?(:call) + issuer_value.to_s.empty? + end + # When discovery is enabled we need a *stable* base URL. # # If the user provides an issuer without an explicit scheme (e.g. "keycloak:8080/realms/foo"), @@ -270,19 +280,23 @@ def issuer # Recommended behavior: only trust options.issuer for discovery if it is an absolute URI with http/https scheme. # Otherwise, fall back to client_options.scheme/host/port. def discovery_base_url - issuer = options.issuer.to_s + # Handle Proc/lambda for options.issuer (common pattern for runtime evaluation) + issuer_value = options.issuer + issuer_value = issuer_value.call if issuer_value.respond_to?(:call) + issuer = issuer_value.to_s # Debug logging to see what we're working with if defined?(Rails) && Rails.logger - Rails.logger.debug "[OpenIDConnect] discovery_base_url - options.issuer: #{options.issuer.inspect}" - Rails.logger.debug "[OpenIDConnect] discovery_base_url - issuer string: #{issuer}" + puts "[OpenIDConnect] discovery_base_url - options.issuer: #{options.issuer.inspect}" + puts "[OpenIDConnect] discovery_base_url - issuer value: #{issuer_value.inspect}" + puts "[OpenIDConnect] discovery_base_url - issuer string: #{issuer}" end if issuer.match?(/\Ahttps?:\/\//) # Use the full issuer URL as-is for discovery # The discovery endpoint will be: issuer + '/.well-known/openid-configuration' if defined?(Rails) && Rails.logger - Rails.logger.debug "[OpenIDConnect] discovery_base_url - using issuer as-is: #{issuer}" + puts "[OpenIDConnect] discovery_base_url - using issuer as-is: #{issuer}" end issuer else @@ -290,7 +304,7 @@ def discovery_base_url resource = "#{client_options.scheme}://#{client_options.host}" resource = "#{resource}:#{client_options.port}" if client_options.port if defined?(Rails) && Rails.logger - Rails.logger.debug "[OpenIDConnect] discovery_base_url - constructed from client_options: #{resource}" + puts "[OpenIDConnect] discovery_base_url - constructed from client_options: #{resource}" end resource end @@ -327,7 +341,8 @@ def configure_discovery_scheme def discover! return unless options.discovery - + puts "I am in discover" + puts config client_options.authorization_endpoint = config.authorization_endpoint client_options.token_endpoint = config.token_endpoint client_options.userinfo_endpoint = config.userinfo_endpoint From 6b86d258716e6b384534938af95bcf61c5e045c1 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 14 Apr 2026 15:31:00 +0200 Subject: [PATCH 6/9] now tested on 4.0 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e59b6c4f..668806eb 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Or install it yourself as: ## Supported Ruby Versions -OmniAuth::OpenIDConnect is tested under 2.7, 3.0, 3.1, 3.2, 3.3, 3.4 +OmniAuth::OpenIDConnect is tested under 2.7, 3.0, 3.1, 3.2, 3.3, 3.4, 4.0 ## Usage From 94e75f05c0477fec91d74b17fa220695e280913a Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 14 Apr 2026 15:31:08 +0200 Subject: [PATCH 7/9] remove debugging output --- lib/omniauth/strategies/openid_connect.rb | 26 ++--------------------- 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/lib/omniauth/strategies/openid_connect.rb b/lib/omniauth/strategies/openid_connect.rb index 1e499760..3d13b7bd 100644 --- a/lib/omniauth/strategies/openid_connect.rb +++ b/lib/omniauth/strategies/openid_connect.rb @@ -114,20 +114,9 @@ def config @config ||= ::OpenIDConnect::Discovery::Provider::Config.discover!(discovery_base_url) end - def request_phase - puts "\nHERE I AM\n" - # Debug logging to diagnose issuer issues - if defined?(Rails) && Rails.logger - puts "[OpenIDConnect] request_phase - options.issuer BEFORE: #{options.issuer.inspect}" - puts "[OpenIDConnect] request_phase - options.issuer.to_s.empty?: #{options.issuer.to_s.empty?}" - end - + def request_phase options.issuer = issuer if issuer_empty? - if defined?(Rails) && Rails.logger - puts "[OpenIDConnect] request_phase - options.issuer AFTER: #{options.issuer.inspect}" - end - discover! redirect authorize_uri end @@ -285,13 +274,6 @@ def discovery_base_url issuer_value = issuer_value.call if issuer_value.respond_to?(:call) issuer = issuer_value.to_s - # Debug logging to see what we're working with - if defined?(Rails) && Rails.logger - puts "[OpenIDConnect] discovery_base_url - options.issuer: #{options.issuer.inspect}" - puts "[OpenIDConnect] discovery_base_url - issuer value: #{issuer_value.inspect}" - puts "[OpenIDConnect] discovery_base_url - issuer string: #{issuer}" - end - if issuer.match?(/\Ahttps?:\/\//) # Use the full issuer URL as-is for discovery # The discovery endpoint will be: issuer + '/.well-known/openid-configuration' @@ -303,9 +285,6 @@ def discovery_base_url # Issuer doesn't have a scheme, construct URL from client_options resource = "#{client_options.scheme}://#{client_options.host}" resource = "#{resource}:#{client_options.port}" if client_options.port - if defined?(Rails) && Rails.logger - puts "[OpenIDConnect] discovery_base_url - constructed from client_options: #{resource}" - end resource end end @@ -341,8 +320,7 @@ def configure_discovery_scheme def discover! return unless options.discovery - puts "I am in discover" - puts config + client_options.authorization_endpoint = config.authorization_endpoint client_options.token_endpoint = config.token_endpoint client_options.userinfo_endpoint = config.userinfo_endpoint From c6d14b03a4176ee0e07583f3530370f3df55641f Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 21 Apr 2026 07:17:24 -0400 Subject: [PATCH 8/9] Remove old debugging code --- lib/omniauth/strategies/openid_connect.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/omniauth/strategies/openid_connect.rb b/lib/omniauth/strategies/openid_connect.rb index 3d13b7bd..b2c13845 100644 --- a/lib/omniauth/strategies/openid_connect.rb +++ b/lib/omniauth/strategies/openid_connect.rb @@ -277,9 +277,6 @@ def discovery_base_url if issuer.match?(/\Ahttps?:\/\//) # Use the full issuer URL as-is for discovery # The discovery endpoint will be: issuer + '/.well-known/openid-configuration' - if defined?(Rails) && Rails.logger - puts "[OpenIDConnect] discovery_base_url - using issuer as-is: #{issuer}" - end issuer else # Issuer doesn't have a scheme, construct URL from client_options From 361385b5ea762f2331c540d8f3f11ee0f69d4671 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 21 Apr 2026 07:55:16 -0400 Subject: [PATCH 9/9] Proc use wasnt actually needed --- lib/omniauth/strategies/openid_connect.rb | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/lib/omniauth/strategies/openid_connect.rb b/lib/omniauth/strategies/openid_connect.rb index b2c13845..0963b87d 100644 --- a/lib/omniauth/strategies/openid_connect.rb +++ b/lib/omniauth/strategies/openid_connect.rb @@ -252,13 +252,9 @@ def issuer ::OpenIDConnect::Discovery::Provider.discover!(resource).issuer end - # Helper method to safely check if issuer is empty, handling Proc/lambda values + # Helper method to safely check if issuer is empty def issuer_empty? - return true if options.issuer.nil? - - issuer_value = options.issuer - issuer_value = issuer_value.call if issuer_value.respond_to?(:call) - issuer_value.to_s.empty? + options.issuer.nil? || options.issuer.to_s.empty? end # When discovery is enabled we need a *stable* base URL. @@ -269,10 +265,7 @@ def issuer_empty? # Recommended behavior: only trust options.issuer for discovery if it is an absolute URI with http/https scheme. # Otherwise, fall back to client_options.scheme/host/port. def discovery_base_url - # Handle Proc/lambda for options.issuer (common pattern for runtime evaluation) - issuer_value = options.issuer - issuer_value = issuer_value.call if issuer_value.respond_to?(:call) - issuer = issuer_value.to_s + issuer = options.issuer.to_s if issuer.match?(/\Ahttps?:\/\//) # Use the full issuer URL as-is for discovery