Class: RubyLLM::MCP::Auth::BrowserOAuthProvider
- Inherits:
-
Object
- Object
- RubyLLM::MCP::Auth::BrowserOAuthProvider
- Defined in:
- lib/ruby_llm/mcp/auth/browser_oauth_provider.rb
Overview
Browser-based OAuth authentication provider Provides complete OAuth 2.1 flow with automatic browser opening and local callback server Compatible API with OAuthProvider for seamless interchange
Defined Under Namespace
Classes: NullLogger, SynchronizedLogger
Instance Attribute Summary collapse
-
#callback_path ⇒ Object
readonly
Returns the value of attribute callback_path.
-
#callback_port ⇒ Object
readonly
Returns the value of attribute callback_port.
-
#logger ⇒ Object
readonly
Returns the value of attribute logger.
-
#oauth_provider ⇒ Object
readonly
Returns the value of attribute oauth_provider.
-
#redirect_uri ⇒ Object
Returns the value of attribute redirect_uri.
-
#scope ⇒ Object
Returns the value of attribute scope.
-
#server_url ⇒ Object
Returns the value of attribute server_url.
-
#storage ⇒ Object
Returns the value of attribute storage.
Instance Method Summary collapse
-
#access_token ⇒ Token?
Get current access token (for compatibility with OAuthProvider).
-
#apply_authorization(request) ⇒ Object
Apply authorization header to HTTP request (for compatibility with OAuthProvider).
-
#authenticate(timeout: 300, auto_open_browser: true) ⇒ Token
Perform complete OAuth authentication flow with browser Compatible with OAuthProvider's authentication pattern.
-
#complete_authorization_flow(code, state) ⇒ Token
Complete authorization flow (for compatibility with OAuthProvider).
- #custom_error_page ⇒ Object
-
#custom_success_page ⇒ Object
Expose custom pages for testing/inspection.
-
#handle_authentication_challenge(www_authenticate: nil, resource_metadata: nil, resource_metadata_url: nil, requested_scope: nil) ⇒ Boolean
Handle authentication challenge with browser-based auth.
-
#initialize(server_url: nil, oauth_provider: nil, callback_port: 8080, callback_path: "/callback", logger: nil, storage: nil, redirect_uri: nil, scope: nil) ⇒ BrowserOAuthProvider
constructor
A new instance of BrowserOAuthProvider.
-
#parse_www_authenticate(header) ⇒ Hash
Parse WWW-Authenticate header (delegate to oauth_provider).
-
#start_authorization_flow ⇒ String
Start authorization flow (for compatibility with OAuthProvider).
Constructor Details
#initialize(server_url: nil, oauth_provider: nil, callback_port: 8080, callback_path: "/callback", logger: nil, storage: nil, redirect_uri: nil, scope: nil) ⇒ BrowserOAuthProvider
Returns a new instance of BrowserOAuthProvider.
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/ruby_llm/mcp/auth/browser_oauth_provider.rb', line 74 def initialize(server_url: nil, oauth_provider: nil, callback_port: 8080, callback_path: "/callback", # rubocop:disable Metrics/ParameterLists logger: nil, storage: nil, redirect_uri: nil, scope: nil) @logger = logger || MCP.logger @synchronized_logger = SynchronizedLogger.new(@logger) @callback_logger = NullLogger.new @callback_port = callback_port @callback_path = callback_path # Set redirect_uri before creating oauth_provider redirect_uri ||= "http://localhost:#{callback_port}#{callback_path}" # Either accept an existing oauth_provider or create one if oauth_provider @oauth_provider = oauth_provider # Sync attributes from the provided oauth_provider @server_url = oauth_provider.server_url @redirect_uri = oauth_provider.redirect_uri @scope = oauth_provider.scope @storage = oauth_provider.storage elsif server_url @server_url = server_url @redirect_uri = redirect_uri @scope = scope @storage = storage || MemoryStorage.new # Create a new oauth_provider @oauth_provider = OAuthProvider.new( server_url: server_url, redirect_uri: redirect_uri, scope: scope, logger: @synchronized_logger, storage: @storage ) else raise ArgumentError, "Either server_url or oauth_provider must be provided" end # Ensure OAuth provider redirect_uri matches our callback server validate_and_sync_redirect_uri! # Initialize browser helpers @http_server = Browser::HttpServer.new(port: @callback_port, logger: @callback_logger) @callback_handler = Browser::CallbackHandler.new(callback_path: @callback_path, logger: @callback_logger) @pages = Browser::Pages.new( custom_success_page: MCP.config.oauth.browser_success_page, custom_error_page: MCP.config.oauth.browser_error_page ) @opener = Browser::Opener.new(logger: @synchronized_logger) end |
Instance Attribute Details
#callback_path ⇒ Object (readonly)
Returns the value of attribute callback_path.
54 55 56 |
# File 'lib/ruby_llm/mcp/auth/browser_oauth_provider.rb', line 54 def callback_path @callback_path end |
#callback_port ⇒ Object (readonly)
Returns the value of attribute callback_port.
54 55 56 |
# File 'lib/ruby_llm/mcp/auth/browser_oauth_provider.rb', line 54 def callback_port @callback_port end |
#logger ⇒ Object (readonly)
Returns the value of attribute logger.
54 55 56 |
# File 'lib/ruby_llm/mcp/auth/browser_oauth_provider.rb', line 54 def logger @logger end |
#oauth_provider ⇒ Object (readonly)
Returns the value of attribute oauth_provider.
54 55 56 |
# File 'lib/ruby_llm/mcp/auth/browser_oauth_provider.rb', line 54 def oauth_provider @oauth_provider end |
#redirect_uri ⇒ Object
Returns the value of attribute redirect_uri.
55 56 57 |
# File 'lib/ruby_llm/mcp/auth/browser_oauth_provider.rb', line 55 def redirect_uri @redirect_uri end |
#scope ⇒ Object
Returns the value of attribute scope.
55 56 57 |
# File 'lib/ruby_llm/mcp/auth/browser_oauth_provider.rb', line 55 def scope @scope end |
#server_url ⇒ Object
Returns the value of attribute server_url.
55 56 57 |
# File 'lib/ruby_llm/mcp/auth/browser_oauth_provider.rb', line 55 def server_url @server_url end |
#storage ⇒ Object
Returns the value of attribute storage.
55 56 57 |
# File 'lib/ruby_llm/mcp/auth/browser_oauth_provider.rb', line 55 def storage @storage end |
Instance Method Details
#access_token ⇒ Token?
Get current access token (for compatibility with OAuthProvider)
182 183 184 |
# File 'lib/ruby_llm/mcp/auth/browser_oauth_provider.rb', line 182 def access_token @oauth_provider.access_token end |
#apply_authorization(request) ⇒ Object
Apply authorization header to HTTP request (for compatibility with OAuthProvider)
188 189 190 |
# File 'lib/ruby_llm/mcp/auth/browser_oauth_provider.rb', line 188 def (request) @oauth_provider.(request) end |
#authenticate(timeout: 300, auto_open_browser: true) ⇒ Token
Perform complete OAuth authentication flow with browser Compatible with OAuthProvider's authentication pattern
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/ruby_llm/mcp/auth/browser_oauth_provider.rb', line 128 def authenticate(timeout: 300, auto_open_browser: true) # 1. Start authorization flow and get URL auth_url = @oauth_provider. @synchronized_logger.debug("Authorization URL: #{auth_url}") # 2. Create result container for thread coordination result = { code: nil, state: nil, error: nil, completed: false } mutex = Mutex.new condition = ConditionVariable.new # 3. Start local callback server server = start_callback_server(result, mutex, condition) begin (auth_url, auto_open_browser) # Allow callback worker to begin processing only after setup logging/browser open # to reduce cross-thread test-double races under JRuby. server.start # 5. Wait for callback with timeout mutex.synchronize do condition.wait(mutex, timeout) unless result[:completed] end snapshot = mutex.synchronize { result.dup } unless snapshot[:completed] raise Errors::TimeoutError.new(message: "OAuth authorization timed out after #{timeout} seconds") end if snapshot[:error] raise Errors::TransportError.new(message: "OAuth authorization failed: #{snapshot[:error]}") end # Stop callback server before token exchange to avoid cross-thread races # (observed under JRuby when background callback logging overlaps main-thread mocks). server&.shutdown server = nil # 6. Complete OAuth flow @synchronized_logger.debug("Completing OAuth authorization flow") token = @oauth_provider.(snapshot[:code], snapshot[:state]) @synchronized_logger.info("\nAuthentication successful!") token ensure # Always shutdown the server server&.shutdown end end |
#complete_authorization_flow(code, state) ⇒ Token
Complete authorization flow (for compatibility with OAuthProvider)
202 203 204 |
# File 'lib/ruby_llm/mcp/auth/browser_oauth_provider.rb', line 202 def (code, state) @oauth_provider.(code, state) end |
#custom_error_page ⇒ Object
62 63 64 |
# File 'lib/ruby_llm/mcp/auth/browser_oauth_provider.rb', line 62 def custom_error_page @pages.instance_variable_get(:@custom_error_page) end |
#custom_success_page ⇒ Object
Expose custom pages for testing/inspection
58 59 60 |
# File 'lib/ruby_llm/mcp/auth/browser_oauth_provider.rb', line 58 def custom_success_page @pages.instance_variable_get(:@custom_success_page) end |
#handle_authentication_challenge(www_authenticate: nil, resource_metadata: nil, resource_metadata_url: nil, requested_scope: nil) ⇒ Boolean
Handle authentication challenge with browser-based auth
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/ruby_llm/mcp/auth/browser_oauth_provider.rb', line 212 def handle_authentication_challenge(www_authenticate: nil, resource_metadata: nil, resource_metadata_url: nil, requested_scope: nil) @synchronized_logger.debug("BrowserOAuthProvider handling authentication challenge") # Try standard provider's automatic handling first (token refresh, client credentials) begin return @oauth_provider.handle_authentication_challenge( www_authenticate: www_authenticate, resource_metadata: , resource_metadata_url: , requested_scope: requested_scope ) rescue Errors::AuthenticationRequiredError # Standard provider couldn't handle it - need interactive auth @synchronized_logger.info("Automatic authentication failed, starting browser-based OAuth flow") end # Perform full browser-based authentication authenticate(auto_open_browser: true) true end |
#parse_www_authenticate(header) ⇒ Hash
Parse WWW-Authenticate header (delegate to oauth_provider)
237 238 239 |
# File 'lib/ruby_llm/mcp/auth/browser_oauth_provider.rb', line 237 def parse_www_authenticate(header) @oauth_provider.parse_www_authenticate(header) end |
#start_authorization_flow ⇒ String
Start authorization flow (for compatibility with OAuthProvider)
194 195 196 |
# File 'lib/ruby_llm/mcp/auth/browser_oauth_provider.rb', line 194 def @oauth_provider. end |