credential-storage-macos
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:
- macOS Keychain - Primary storage for OAuth and subscription tokens
- File-based storage - Alternative for containers/Linux environments (
~/.claude/.credentials.json) - 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:
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):
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">-wWhat 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:
| Method | Storage Location | Use Case |
|---|---|---|
| Subscription OAuth | macOS Keychain (Claude Code-credentials) | Claude Pro/Max subscribers |
| API Key | macOS Keychain or apiKeyHelper script | Direct API access with credit billing |
| Azure Auth | Keychain | Azure OpenAI integration |
| AWS Bedrock | AWS credentials | Bedrock model access |
| Google Vertex AI | GCP credentials | Vertex 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 #1: 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:
# Copy credentials to expected service nameCREDS=#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: #9403
Issue #2: 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:
security#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">unlock-keychain#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">~/Library/Keychains/login.keychain-dbWorkaround Option 2 - Use API Key Authentication:
# 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:
# Generate long-lived tokenclaude#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">setup-token
# Set environment variableexport#2A2E3A;--1:#DADDE5"> CLAUDE_CODE_OAUTH_TOKEN=#2965A8;--1:#80BBFF">"#25723F;--1:#82D99F">your-token-here#2965A8;--1:#80BBFF">"GitHub Issues: #5957, phoenixtrap.com workaround
Issue #3: 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: #10039
Issue #4: 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
/loginfrequently - Token refresh may fail silently
Workaround: Set up apiKeyHelper to automatically refresh or switch to API key authentication for long-running processes.
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
| Aspect | OAuth | API Key |
|---|---|---|
| Rotation | Automatic (8-hour tokens) | Manual only |
| Revocation | Via Claude dashboard | Via API dashboard |
| Scope Limitation | Yes (user:inference, etc.) | No (full access) |
| Session Tracking | Yes | Limited |
| Recommended For | Interactive use | Automation, CI/CD |
Best Practices
- Use OAuth for interactive sessions: Better security with automatic token rotation
- Use API Keys for automation: More reliable for CI/CD, containers, SSH sessions
- Never commit credentials: Add
.credentials.jsonto.gitignore - Rotate API keys regularly: Especially if used in shared environments
- Use environment variables for containers:
CLAUDE_CODE_OAUTH_TOKENorANTHROPIC_API_KEY - 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:
claude#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">setup-tokenUse 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:
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
# Check if credentials exist in keychainsecurity#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 filecat#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 variablesenv#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
# Remove keychain entrysecurity#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 filerm#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">~/.claude/.credentials.json#2A2E3A;--1:#DADDE5"> 2>#25723F;--1:#82D99F">/dev/null
# Re-loginclaude# Then run: /loginVerify Authentication Method
Run Claude Code and check the authentication method being used:
claude#2A2E3A;--1:#DADDE5"> #25723F;--1:#82D99F">--help# Check for: "Authenticated as: [email]" or "Using API key: sk-ant-..."Related Topics
- Identity and Access Management - Claude Code Docs
- GitHub App Integration - OAuth authentication for GitHub Actions
- Hooks System - Session tracking with authentication context
Credential Storage Comparison
| Storage Method | Pros | Cons | Best For |
|---|---|---|---|
| macOS Keychain | Secure, automatic, integrated | SSH issues, platform-specific | Interactive macOS use |
| Credentials File | Cross-platform, simple | Less secure, deleted on macOS | Containers, Linux |
| Environment Variables | Flexible, container-friendly | Visible in process list | CI/CD, automation |
| apiKeyHelper | Dynamic, scriptable, secure vaults | Complexity, refresh overhead | Enterprise, shared teams |
Sources
- Identity and Access Management - Claude Code Docs
- BUG: macOS Keychain issue - Issue #9403
- Claude Code SSH Authentication Fix for macOS Keychain
- macOS Keychain: SSH Authentication Failure - Issue #5957
- BUG: Persistent Logout & Configuration Loss - Issue #1676
- BUG: OAuth token not persisted - Issue #5244
- BUG: Credentials file deletion - Issue #10039
- Setup Container Authentication - Claude Did This
- OAuth token revoked - Issue #4107
- How I Built claude_max - Substack Article
Last Updated: 2025-12-28 Status: Comprehensive documentation with known issues and workarounds