AI Assistant

Authentication

The authentication service (src/services/oauth/) implements OAuth 2.0 Authorization Code flow with PKCE (Proof Key for Code Exchange) for secure authentication against Claude services. It supports both automatic (browser-based) and manual (copy-paste) authentication flows.

OAuth 2.0 with PKCE

PKCE adds a layer of security to the OAuth flow by preventing authorization code interception attacks. The implementation generates cryptographic values using crypto.ts:

// Generate a random code verifier (43-character base64url string)
export function generateCodeVerifier(): string {
  return base64URLEncode(randomBytes(32))
}

// Derive the code challenge via SHA-256 hash
export function generateCodeChallenge(verifier: string): string {
  const hash = createHash('sha256')
  hash.update(verifier)
  return base64URLEncode(hash.digest())
}

// Generate random state parameter for CSRF protection
export function generateState(): string {
  return base64URLEncode(randomBytes(32))
}

Authentication Flow

1

Initialize

The OAuthService constructor generates a fresh codeVerifier for the PKCE flow.

2

Start Local Server

An AuthCodeListener starts a local HTTP server on a random port to receive the OAuth callback.

3

Generate URLs

Two authorization URLs are built: one for automatic flow (redirects to localhost) and one for manual flow (redirects to a page showing the auth code).

4

Race: Automatic vs Manual

Both flows run concurrently. The first to provide an authorization code wins:

  • Automatic: Browser opens the authorization URL, user logs in, redirect captures the code
  • Manual: User copies the code from the browser and pastes it into the CLI
5

Exchange Code for Tokens

The authorization code is exchanged for access and refresh tokens via exchangeCodeForTokens().

6

Fetch Profile

Profile information (subscription type, rate limit tier) is fetched using the new access token.

7

Return Tokens

The complete OAuthTokens object is returned to the caller for storage.

Automatic vs Manual Auth

The default flow for environments with browser access:

  1. Opens the authorization URL in the user's browser
  2. User authenticates on the Claude website
  3. Browser redirects to http://localhost:{port}/callback with the auth code
  4. The AuthCodeListener captures the code and sends a success redirect
const automaticFlowUrl = client.buildAuthUrl({
  codeChallenge,
  state,
  port: this.port,
  isManual: false,
})
await openBrowser(automaticFlowUrl)

Token Management

The OAuthTokens object contains all authentication state:

accessTokenstring

Bearer token for API requests. Included in Authorization headers.

refreshTokenstring

Long-lived token used to obtain new access tokens when the current one expires.

expiresAtnumber

Unix timestamp (milliseconds) when the access token expires. Calculated as Date.now() + expires_in * 1000.

scopesstring[]

Granted OAuth scopes parsed from the space-delimited scope string.

subscriptionTypeSubscriptionType | null

User's subscription tier (e.g., Free, Pro, Max, Team, Enterprise).

rateLimitTierRateLimitTier | null

Rate limit tier determining API quotas.

profileOAuthProfileResponse

Full profile response including account details.

tokenAccountobject

Account UUID, email address, and organization UUID from the token exchange response.

Token Refresh

The checkAndRefreshOAuthTokenIfNeeded() utility (in utils/auth.ts) automatically refreshes expired tokens before API calls. The retry system in withRetry.ts also handles 401 errors by triggering handleOAuth401Error() to force a token refresh.

Token Expiry Check

export function isOAuthTokenExpired(expiresAt: number): boolean

Used by the usage tracking service to skip API calls when the token is expired, avoiding unnecessary 401 errors.

Profile Fetching

After token exchange, the service fetches the user's profile to determine:

  • Subscription type: Free, Pro, Max, Team, Enterprise/C4E
  • Rate limit tier: Controls API quotas and retry behavior
  • Billing type: Affects overage credit availability
  • Organization: Used for organization-scoped features and policy limits
const profileInfo = await client.fetchProfileInfo(tokenResponse.access_token)

Organization Selection

The buildAuthUrl() function supports organization-specific authentication:

orgUUIDstring

Pre-select an organization during the OAuth flow.

loginHintstring

Pre-fill the email field on the login page.

loginMethodstring

Suggest a specific authentication method.

loginWithClaudeAiboolean

Use the Claude.ai authorization endpoint instead of the Console endpoint.

inferenceOnlyboolean

Request only inference-related scopes (not full Console access).

Auth URL Construction

The authorization URL is built with standard OAuth 2.0 parameters:

const authUrl = new URL(authUrlBase)
authUrl.searchParams.append('client_id', getOauthConfig().CLIENT_ID)
authUrl.searchParams.append('response_type', 'code')
authUrl.searchParams.append('redirect_uri', redirectUri)
authUrl.searchParams.append('code_challenge', codeChallenge)
authUrl.searchParams.append('code_challenge_method', 'S256')
authUrl.searchParams.append('state', state)
authUrl.searchParams.append('scope', scopes)

The state parameter provides CSRF protection. It is validated during the callback to ensure the authorization response corresponds to the original request.