PKCE OAuth flow with proactive token refresh, OS keychain storage, and startup prefetch.
sequenceDiagram
participant U as User
participant C as Claude Code
participant K as OS Keychain
participant A as Auth Server
U->>C: claude (first run)
C->>A: PKCE auth request
A->>U: Browser login
U->>A: Approve
A->>C: Auth code
C->>A: Exchange for token (PKCE verify)
A->>C: Access + Refresh tokens
C->>K: Store securely
Note over C: Schedule refresh timer
C->>K: Prefetch at next startup
C->>A: Refresh (5min before expiry)Mermaid diagram definition
OAuth for a CLI is unusual — there's no browser redirect URL. Claude Code uses PKCE (Proof Key for Code Exchange), which allows public clients to safely exchange auth codes without a client secret.
The refresh timer is set immediately when a token is stored. When the timer fires (5 minutes before expiry), a background refresh runs. The user never experiences a mid-session token expiry.
At startup, keychain reads are fired in parallel with module loading. By the time the REPL is ready, the token is already in memory — no keychain latency on the first API call.
Auth module showing multi-provider support and the token refresh scheduling.
import chalk from 'chalk'
import { exec } from 'child_process'
import { execa } from 'execa'
import { mkdir, stat } from 'fs/promises'
import memoize from 'lodash-es/memoize.js'
import { join } from 'path'
import { CLAUDE_AI_PROFILE_SCOPE } from 'src/constants/oauth.js'
import {
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
logEvent,
} from 'src/services/analytics/index.js'
import { getModelStrings } from 'src/utils/model/modelStrings.js'
import { getAPIProvider } from 'src/utils/model/providers.js'
import {
getIsNonInteractiveSession,
preferThirdPartyAuthentication,
} from '../bootstrap/state.js'
import {
getMockSubscriptionType,
shouldUseMockSubscription,
} from '../services/mockRateLimits.js'
import {
isOAuthTokenExpired,
refreshOAuthToken,
shouldUseClaudeAIAuth,
} from '../services/oauth/client.js'
import { getOauthProfileFromOauthToken } from '../services/oauth/getOauthProfile.js'
import type { OAuthTokens, SubscriptionType } from '../services/oauth/types.js'
import {
getApiKeyFromFileDescriptor,
getOAuthTokenFromFileDescriptor,
} from './authFileDescriptor.js'
import {
maybeRemoveApiKeyFromMacOSKeychainThrows,
normalizeApiKeyForConfig,
} from './authPortable.js'
import {
checkStsCallerIdentity,
clearAwsIniCache,
isValidAwsStsOutput,
} from './aws.js'
import { AwsAuthStatusManager } from './awsAuthStatusManager.js'
import { clearBetasCaches } from './betas.js'
import {
type AccountInfo,
checkHasTrustDialogAccepted,
getGlobalConfig,
saveGlobalConfig,
} from './config.js'
import { logAntError, logForDebugging } from './debug.js'Ask anything about OAuth Token Lifecycle
Powered by Groq · Enter to send, Shift+Enter for newline