Auth Proactive Refresh + Secure Storage See in Code Tour

OAuth Token Lifecycle

PKCE OAuth flow with proactive token refresh, OS keychain storage, and startup prefetch.

OAuth Token Lifecycle — Architecture Diagram
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

Deep Dive

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.

🔑Key Insight

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.

ℹ️Info

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.

KEY TAKEAWAYS
  • PKCE enables safe OAuth in public clients without client secrets
  • Proactive refresh eliminates mid-session token expiry errors
  • OS keychain is more secure than file-based credential storage
  • Parallel prefetch removes keychain latency from the hot path

Source Code

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'
AI Assistant

Ask anything about OAuth Token Lifecycle

Powered by Groq · Enter to send, Shift+Enter for newline