Module: AgentHarness::Authentication

Defined in:
lib/agent_harness/authentication.rb

Overview

Authentication management for CLI agent providers

Provides methods for checking auth status, generating OAuth URLs, and refreshing credentials for providers that support it.

Class Method Summary collapse

Class Method Details

.auth_capabilities(provider_name) ⇒ Hash

Get authentication flow capabilities for a provider.

Parameters:

  • provider_name (Symbol)

    the provider name

Returns:

  • (Hash)

    capabilities with :auth_type, :auth_url, :refresh keys

Raises:



45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/agent_harness/authentication.rb', line 45

def auth_capabilities(provider_name)
  provider_name = provider_name.to_sym
  provider = resolve_provider(provider_name)
  canonical_name = Providers::Registry.instance.canonical_name(provider_name)
  flow_supported = claude_oauth_flow_provider?(provider_name, canonical_name)

  {
    auth_type: provider.auth_type,
    auth_url: flow_supported,
    refresh: flow_supported,
    exchange: flow_supported,
    code_exchange: flow_supported
  }
end

.auth_status(provider_name) ⇒ Hash

Get detailed authentication status for a provider

Parameters:

  • provider_name (Symbol)

    the provider name

Returns:

  • (Hash)

    status with :valid, :expires_at, :error keys



30
31
32
33
34
35
36
37
38
# File 'lib/agent_harness/authentication.rb', line 30

def auth_status(provider_name)
  provider_name = provider_name.to_sym
  case provider_name
  when :claude, :anthropic
    claude_auth_status
  else
    generic_auth_status(provider_name)
  end
end

.auth_url(provider_name) ⇒ String

Generate an OAuth URL for a provider

Only supported for :oauth auth type providers.

Parameters:

  • provider_name (Symbol)

    the provider name

Returns:

  • (String)

    the OAuth authorization URL

Raises:



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/agent_harness/authentication.rb', line 76

def auth_url(provider_name)
  provider_name = provider_name.to_sym
  provider = resolve_provider(provider_name)

  unless provider.auth_type == :oauth
    raise UnsupportedAuthFlowError,
      "Provider #{provider_name} uses #{provider.auth_type} auth and does not support OAuth URL generation"
  end

  case provider_name
  when :claude, :anthropic
    claude_auth_url
  else
    raise UnsupportedAuthFlowError,
      "OAuth URL generation is not yet implemented for provider #{provider_name}"
  end
end

.auth_url_supported?(provider_name) ⇒ Boolean

Check whether OAuth URL generation is supported for a provider.

Parameters:

  • provider_name (Symbol)

    the provider name

Returns:

  • (Boolean)

    true if auth_url can be called for the provider

Raises:



65
66
67
# File 'lib/agent_harness/authentication.rb', line 65

def auth_url_supported?(provider_name)
  auth_capabilities(provider_name)[:auth_url]
end

.auth_valid?(provider_name) ⇒ Boolean

Check if authentication is valid for a provider

Parameters:

  • provider_name (Symbol)

    the provider name

Returns:

  • (Boolean)

    true if auth is valid, false otherwise



21
22
23
24
# File 'lib/agent_harness/authentication.rb', line 21

def auth_valid?(provider_name)
  status = auth_status(provider_name)
  !!status[:valid]
end

.exchange_code(provider_name, code:, code_verifier:, redirect_uri:, client_id:, state: nil) ⇒ Hash

Exchange a PKCE authorization code for tokens and persist them in native shape.

Posts the authorization code and PKCE verifier to the provider’s OAuth token endpoint, then writes the resulting access/refresh tokens to the credentials store using the provider’s native shape (e.g. claudeAiOauth for Claude).

Serializes through a file lock so that concurrent callers do not race on credential writes.

Parameters:

  • provider_name (Symbol)

    the provider name

  • code (String)

    authorization code returned from the OAuth redirect (required)

  • code_verifier (String)

    PKCE code verifier matching the code_challenge sent on the auth URL (required)

  • redirect_uri (String)

    redirect URI registered with the authorization request (required)

  • client_id (String)

    OAuth client identifier (required)

  • state (String, nil) (defaults to: nil)

    optional state parameter echoed from the authorization request

Returns:

  • (Hash)

    credential in claudeAiOauth shape for Claude: { claudeAiOauth: { accessToken:, refreshToken:, expiresAt: } }

Raises:



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/agent_harness/authentication.rb', line 206

def exchange_code(provider_name, code:, code_verifier:, redirect_uri:, client_id:, state: nil)
  provider_name = provider_name.to_sym
  provider = resolve_provider(provider_name)

  unless provider.auth_type == :oauth
    raise UnsupportedAuthFlowError,
      "Provider #{provider_name} uses #{provider.auth_type} auth and does not support code exchange"
  end

  validate_code_exchange_params!(code: code, code_verifier: code_verifier,
    redirect_uri: redirect_uri, client_id: client_id)

  case provider_name
  when :claude, :anthropic
    exchange_claude_code(
      code: code.strip,
      code_verifier: code_verifier.strip,
      redirect_uri: redirect_uri.strip,
      client_id: client_id.strip,
      state: state
    )
  else
    raise UnsupportedAuthFlowError,
      "Code exchange is not yet implemented for provider #{provider_name}"
  end
end

.exchange_code_supported?(provider_name) ⇒ Boolean

Check whether PKCE authorization-code exchange is supported for a provider.

Parameters:

  • provider_name (Symbol)

    the provider name

Returns:

  • (Boolean)

    true if exchange_code can be called

Raises:



181
182
183
# File 'lib/agent_harness/authentication.rb', line 181

def exchange_code_supported?(provider_name)
  auth_capabilities(provider_name)[:code_exchange]
end

.exchange_refresh_token(provider_name) ⇒ Hash

Exchange a stored refresh token for a fresh access token (and rotated refresh token).

Reads the refresh token from the provider’s credentials store, posts it to the OAuth token endpoint, persists the rotated tokens, and returns the credential in native claudeAiOauth shape.

Serializes through a file lock so that concurrent callers do not race on a single-use/rotating refresh token. If the token server reports that the refresh token has already been consumed (‘refresh_token_reused`), raises AuthenticationError so the caller can trigger a full re-auth.

Parameters:

  • provider_name (Symbol)

    the provider name

Returns:

  • (Hash)

    credential in claudeAiOauth shape: { claudeAiOauth: { accessToken:, refreshToken:, expiresAt: } }

Raises:



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/agent_harness/authentication.rb', line 158

def exchange_refresh_token(provider_name)
  provider_name = provider_name.to_sym
  provider = resolve_provider(provider_name)

  unless provider.auth_type == :oauth
    raise UnsupportedAuthFlowError,
      "Provider #{provider_name} uses #{provider.auth_type} auth and does not support token exchange"
  end

  case provider_name
  when :claude, :anthropic
    exchange_claude_refresh_token
  else
    raise UnsupportedAuthFlowError,
      "Token exchange is not yet implemented for provider #{provider_name}"
  end
end

.exchange_refresh_token_supported?(provider_name) ⇒ Boolean

Check whether refresh-token exchange is supported for a provider.

Parameters:

  • provider_name (Symbol)

    the provider name

Returns:

  • (Boolean)

    true if exchange_refresh_token can be called

Raises:



138
139
140
# File 'lib/agent_harness/authentication.rb', line 138

def exchange_refresh_token_supported?(provider_name)
  auth_capabilities(provider_name)[:exchange]
end

.refresh_auth(provider_name, token: nil) ⇒ Hash

Refresh authentication credentials for a provider

For OAuth providers, stores a pre-exchanged token directly. This method accepts a token (not an authorization code) because the OAuth code-exchange flow is provider-specific and should be handled by the caller or a CLI login command before calling this. For API key providers, raises UnsupportedAuthFlowError.

Parameters:

  • provider_name (Symbol)

    the provider name

  • token (String) (defaults to: nil)

    OAuth token to store (must be non-blank)

Returns:

  • (Hash)

    result with :success key

Raises:



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/agent_harness/authentication.rb', line 115

def refresh_auth(provider_name, token: nil)
  provider_name = provider_name.to_sym
  provider = resolve_provider(provider_name)

  unless provider.auth_type == :oauth
    raise UnsupportedAuthFlowError,
      "Provider #{provider_name} uses #{provider.auth_type} auth and does not support credential refresh"
  end

  case provider_name
  when :claude, :anthropic
    refresh_claude_auth(token: token)
  else
    raise UnsupportedAuthFlowError,
      "Credential refresh is not yet implemented for provider #{provider_name}"
  end
end

.refresh_auth_supported?(provider_name) ⇒ Boolean

Check whether credential refresh is supported for a provider.

Parameters:

  • provider_name (Symbol)

    the provider name

Returns:

  • (Boolean)

    true if refresh_auth can be called for the provider

Raises:



99
100
101
# File 'lib/agent_harness/authentication.rb', line 99

def refresh_auth_supported?(provider_name)
  auth_capabilities(provider_name)[:refresh]
end