Purpose

Comprehensive documentation on where and how Claude Code stores authentication credentials on macOS, covering OAuth tokens, API keys, subscription authentication, keychain integration issues, and workarounds for SSH sessions.

Overview

Claude Code uses multiple credential storage mechanisms on macOS depending on the authentication method:

  1. macOS Keychain - Primary storage for OAuth and subscription tokens
  2. File-based storage - Alternative for containers/Linux environments (~/.claude/.credentials.json)
  3. Environment variables - For programmatic access and containers

Keychain Storage Details

Primary Keychain Location

Service Name: Claude Code-credentials Keychain File: ~/Library/Keychains/login.keychain-db Account: Current macOS username Entry Type: Generic password (genp class) Security: Encrypted with macOS Keychain encryption

Verification Command

Check if credentials exist in keychain:

Terminal window
security#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">find-generic-password#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">-s#2A2E3A;--1:#DADDE5"> #2965A8;--1:#80BBFF">"#25723F;--1:#82D99F">Claude Code-credentials#2965A8;--1:#80BBFF">"

Extract credential value (requires keychain unlock):

Terminal window
security#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">find-generic-password#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">-s#2A2E3A;--1:#DADDE5"> #2965A8;--1:#80BBFF">"#25723F;--1:#82D99F">Claude Code-credentials#2965A8;--1:#80BBFF">"#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">-w

What Gets Stored

The keychain entry contains:

  • OAuth Access Token: Short-lived token (expires after ~8 hours)
  • OAuth Refresh Token: Long-lived token for obtaining new access tokens
  • Token Expiry: Timestamp for access token expiration
  • Scopes: Permissions like user:inference, user:profile

Authentication Methods

Claude Code supports multiple authentication types, each with different storage:

MethodStorage LocationUse Case
Subscription OAuthmacOS Keychain (Claude Code-credentials)Claude Pro/Max subscribers
API KeymacOS Keychain or apiKeyHelper scriptDirect API access with credit billing
Azure AuthKeychainAzure OpenAI integration
AWS BedrockAWS credentialsBedrock model access
Google Vertex AIGCP credentialsVertex AI model access

File-Based Credential Storage

Credentials File Location

Path: ~/.claude/.credentials.json Usage: Containers, Linux, or when keychain is unavailable Format: JSON with OAuth tokens

Note: On macOS, when you log in via OAuth, credentials are stored in the keychain and this file gets deleted automatically.

Credential File Structure

#2965A8;--1:#80BBFF">{
#2965A8;--1:#80BBFF">"accessToken#2965A8;--1:#80BBFF">"#2965A8;--1:#80BBFF">:#2A2E3A;--1:#DADDE5"> #2965A8;--1:#80BBFF">"#25723F;--1:#82D99F">...#2965A8;--1:#80BBFF">"#2965A8;--1:#80BBFF">,
#2965A8;--1:#80BBFF">"refreshToken#2965A8;--1:#80BBFF">"#2965A8;--1:#80BBFF">:#2A2E3A;--1:#DADDE5"> #2965A8;--1:#80BBFF">"#25723F;--1:#82D99F">...#2965A8;--1:#80BBFF">"#2965A8;--1:#80BBFF">,
#2965A8;--1:#80BBFF">"expiresAt#2965A8;--1:#80BBFF">"#2965A8;--1:#80BBFF">:#2A2E3A;--1:#DADDE5"> #2965A8;--1:#80BBFF">"#25723F;--1:#82D99F">2025-12-29T12:00:00Z#2965A8;--1:#80BBFF">"#2965A8;--1:#80BBFF">,
#2965A8;--1:#80BBFF">"scopes#2965A8;--1:#80BBFF">"#2965A8;--1:#80BBFF">:#2A2E3A;--1:#DADDE5"> #2965A8;--1:#80BBFF">[#2965A8;--1:#80BBFF">"#25723F;--1:#82D99F">user:inference#2965A8;--1:#80BBFF">"#2965A8;--1:#80BBFF">,#2A2E3A;--1:#DADDE5"> #2965A8;--1:#80BBFF">"#25723F;--1:#82D99F">user:profile#2965A8;--1:#80BBFF">"#2965A8;--1:#80BBFF">]
#2965A8;--1:#80BBFF">}

Known Issues & Bugs

Issue : Service Name Mismatch (v2.0.14)

Problem: OAuth flow writes to Claude Code-credentials but startup reads from Claude Code (without -credentials suffix), causing authentication failures.

Symptoms:

  • OAuth login succeeds in browser
  • Claude Code shows “Missing API key” error on startup
  • Need to re-authenticate every session

Workaround:

Terminal window
# Copy credentials to expected service name
CREDS=#2965A8;--1:#80BBFF">$(security#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">find-generic-password#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">-s#2A2E3A;--1:#DADDE5"> #2965A8;--1:#80BBFF">"#25723F;--1:#82D99F">Claude Code-credentials#2965A8;--1:#80BBFF">"#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">-w#2965A8;--1:#80BBFF">)
security#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">add-generic-password#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">-a#2A2E3A;--1:#DADDE5"> #2965A8;--1:#80BBFF">"$USER#2965A8;--1:#80BBFF">"#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">-s#2A2E3A;--1:#DADDE5"> #2965A8;--1:#80BBFF">"#25723F;--1:#82D99F">Claude Code#2965A8;--1:#80BBFF">"#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">-w#2A2E3A;--1:#DADDE5"> #2965A8;--1:#80BBFF">"$CREDS#2965A8;--1:#80BBFF">"

GitHub Issue:

Issue : SSH Session Keychain Access

Problem: SSH sessions cannot access macOS Keychain which requires GUI session context.

Symptoms:

  • OAuth authentication completes in browser
  • Tokens not accessible in SSH sessions
  • “OAuth account information not found” error

Workaround Option 1 - Unlock Keychain:

Terminal window
security#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">unlock-keychain#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">~/Library/Keychains/login.keychain-db

Workaround Option 2 - Use API Key Authentication:

Terminal window
# Set up API key helper in ~/.claude/settings.json
#2965A8;--1:#80BBFF">{
"apiKeyHelper":#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">{
"command":#2A2E3A;--1:#DADDE5"> #2965A8;--1:#80BBFF">"#25723F;--1:#82D99F">security find-generic-password -s 'claude-api-key' -w#2965A8;--1:#80BBFF">"
#2965A8;--1:#80BBFF">}
#2A2E3A;--1:#DADDE5">}

Workaround Option 3 - Long-lived OAuth Token:

Terminal window
# Generate long-lived token
claude#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">setup-token
# Set environment variable
export#2A2E3A;--1:#DADDE5"> CLAUDE_CODE_OAUTH_TOKEN=#2965A8;--1:#80BBFF">"#25723F;--1:#82D99F">your-token-here#2965A8;--1:#80BBFF">"

GitHub Issues: , phoenixtrap.com workaround

Issue : Credential Persistence Across Platforms

Problem: When you log in on macOS, credentials move to keychain and ~/.claude/.credentials.json gets deleted, breaking Linux sessions sharing the same home directory.

Symptoms:

  • Credentials work on macOS
  • Same home directory on Linux shows “Missing API key”
  • Need separate authentication per platform

Workaround: Use separate home directories or API key authentication for cross-platform setups.

GitHub Issue: 0039

Issue : OAuth Token Expiration

Problem: Access tokens expire after 8 hours, refresh tokens eventually expire requiring re-authentication.

Symptoms:

  • “OAuth token has expired” error
  • Need to run /login frequently
  • Token refresh may fail silently

Workaround: Set up apiKeyHelper to automatically refresh or switch to API key authentication for long-running processes.

GitHub Issues: 107, 746

Security Considerations

Keychain Security

Secure:

  • Credentials encrypted at rest in macOS Keychain
  • Requires keychain unlock (user password or Touch ID)
  • Isolated per-user storage

⚠️ Risks:

  • SSH sessions may expose keychain via security unlock-keychain
  • Credential extraction possible with security find-generic-password -w
  • Shared machines: other users cannot access your keychain

API Key vs OAuth Security

AspectOAuthAPI Key
RotationAutomatic (8-hour tokens)Manual only
RevocationVia Claude dashboardVia API dashboard
Scope LimitationYes (user:inference, etc.)No (full access)
Session TrackingYesLimited
Recommended ForInteractive useAutomation, CI/CD

Best Practices

  1. Use OAuth for interactive sessions: Better security with automatic token rotation
  2. Use API Keys for automation: More reliable for CI/CD, containers, SSH sessions
  3. Never commit credentials: Add .credentials.json to .gitignore
  4. Rotate API keys regularly: Especially if used in shared environments
  5. Use environment variables for containers: CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY
  6. Separate credentials per environment: Don’t share production keys with development

Configuration: apiKeyHelper

For custom credential management, use the apiKeyHelper setting:

#2965A8;--1:#80BBFF">{
#2965A8;--1:#80BBFF">"apiKeyHelper#2965A8;--1:#80BBFF">"#2965A8;--1:#80BBFF">:#2A2E3A;--1:#DADDE5"> #2965A8;--1:#80BBFF">{
#2965A8;--1:#80BBFF">"command#2965A8;--1:#80BBFF">"#2965A8;--1:#80BBFF">:#2A2E3A;--1:#DADDE5"> #2965A8;--1:#80BBFF">"#25723F;--1:#82D99F">your-custom-script.sh#2965A8;--1:#80BBFF">"#2965A8;--1:#80BBFF">,
#2965A8;--1:#80BBFF">"refreshInterval#2965A8;--1:#80BBFF">"#2965A8;--1:#80BBFF">:#2A2E3A;--1:#DADDE5"> 300
#2965A8;--1:#80BBFF">}
#2965A8;--1:#80BBFF">}

Default behavior:

  • Called after 5 minutes
  • Called on HTTP 401 responses
  • Should output API key to stdout

Example use cases:

  • Fetch from 1Password CLI: op read "op://vault/Claude API Key/credential"
  • Fetch from AWS Secrets Manager
  • Fetch from HashiCorp Vault
  • Dynamic token rotation

Container and Programmatic Access

Long-lived OAuth Tokens

Generate a long-lived OAuth token for containers:

Terminal window
claude#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">setup-token

Use in container:

ENV#2A2E3A;--1:#DADDE5"> CLAUDE_CODE_OAUTH_TOKEN=#25723F;--1:#82D99F">"your-long-lived-token"

Note: These tokens need refresh every ~6 hours for security.

Direct API Key

Set environment variable:

Terminal window
export#2A2E3A;--1:#DADDE5"> ANTHROPIC_API_KEY=#2965A8;--1:#80BBFF">"#25723F;--1:#82D99F">sk-ant-...#2965A8;--1:#80BBFF">"

Or in container:

ENV#2A2E3A;--1:#DADDE5"> ANTHROPIC_API_KEY=#25723F;--1:#82D99F">"sk-ant-..."

Debugging Authentication

Check Current Authentication Status

Terminal window
# Check if credentials exist in keychain
security#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">find-generic-password#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">-s#2A2E3A;--1:#DADDE5"> #2965A8;--1:#80BBFF">"#25723F;--1:#82D99F">Claude Code-credentials#2965A8;--1:#80BBFF">"#2A2E3A;--1:#DADDE5"> 2>&1
# Check credentials file
cat#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">~/.claude/.credentials.json#2A2E3A;--1:#DADDE5"> 2>#25723F;--1:#82D99F">/dev/null#2A2E3A;--1:#DADDE5"> ||#2A2E3A;--1:#DADDE5"> echo#2A2E3A;--1:#DADDE5"> #2965A8;--1:#80BBFF">"#25723F;--1:#82D99F">No file-based credentials#2965A8;--1:#80BBFF">"
# Check environment variables
env#2A2E3A;--1:#DADDE5"> |#2A2E3A;--1:#DADDE5"> grep#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">-E#2A2E3A;--1:#DADDE5"> #2965A8;--1:#80BBFF">"#25723F;--1:#82D99F">(CLAUDE|ANTHROPIC)#2965A8;--1:#80BBFF">"

Force Re-authentication

Terminal window
# Remove keychain entry
security#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">delete-generic-password#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">-s#2A2E3A;--1:#DADDE5"> #2965A8;--1:#80BBFF">"#25723F;--1:#82D99F">Claude Code-credentials#2965A8;--1:#80BBFF">"#2A2E3A;--1:#DADDE5"> 2>#25723F;--1:#82D99F">/dev/null
# Remove credentials file
rm#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">~/.claude/.credentials.json#2A2E3A;--1:#DADDE5"> 2>#25723F;--1:#82D99F">/dev/null
# Re-login
claude
# Then run: /login

Verify Authentication Method

Run Claude Code and check the authentication method being used:

Terminal window
claude#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">--help
# Check for: "Authenticated as: [email]" or "Using API key: sk-ant-..."

Credential Storage Comparison

Storage MethodProsConsBest For
macOS KeychainSecure, automatic, integratedSSH issues, platform-specificInteractive macOS use
Credentials FileCross-platform, simpleLess secure, deleted on macOSContainers, Linux
Environment VariablesFlexible, container-friendlyVisible in process listCI/CD, automation
apiKeyHelperDynamic, scriptable, secure vaultsComplexity, refresh overheadEnterprise, shared teams

Sources

  1. Identity and Access Management - Claude Code Docs
  2. BUG: macOS Keychain issue - Issue
  3. Claude Code SSH Authentication Fix for macOS Keychain
  4. macOS Keychain: SSH Authentication Failure - Issue
  5. BUG: Persistent Logout & Configuration Loss - Issue 676
  6. BUG: OAuth token not persisted - Issue
  7. BUG: Credentials file deletion - Issue 0039
  8. Setup Container Authentication - Claude Did This
  9. OAuth token revoked - Issue 107
  10. How I Built claude_max - Substack Article

Last Updated: 2025-12-28 Status: Comprehensive documentation with known issues and workarounds