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
Initialize
The OAuthService constructor generates a fresh codeVerifier for the PKCE flow.
Start Local Server
An AuthCodeListener starts a local HTTP server on a random port to receive the OAuth callback.
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).
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
Exchange Code for Tokens
The authorization code is exchanged for access and refresh tokens via exchangeCodeForTokens().
Fetch Profile
Profile information (subscription type, rate limit tier) is fetched using the new access token.
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:
- Opens the authorization URL in the user's browser
- User authenticates on the Claude website
- Browser redirects to
http://localhost:{port}/callbackwith the auth code - The
AuthCodeListenercaptures 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:
Bearer token for API requests. Included in Authorization headers.
Long-lived token used to obtain new access tokens when the current one expires.
Unix timestamp (milliseconds) when the access token expires. Calculated as Date.now() + expires_in * 1000.
Granted OAuth scopes parsed from the space-delimited scope string.
User's subscription tier (e.g., Free, Pro, Max, Team, Enterprise).
Rate limit tier determining API quotas.
Full profile response including account details.
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): booleanUsed 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:
Pre-select an organization during the OAuth flow.
Pre-fill the email field on the login page.
Suggest a specific authentication method.
Use the Claude.ai authorization endpoint instead of the Console endpoint.
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.