Credential providers in src/anthropic/lib/credentials/ resolve authentication through a chain: explicit credentials argument → ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN env vars → named profile (from env or pointer file) → workload identity federation → fallback default profile, with each source returning credentials or None to advance the chain. Providers like CredentialsFile, StaticToken, and EnvToken implement AccessTokenProvider for OAuth and federation flows, while env var synthesis via _fill_missing_from_env() lets explicit API keys and identity tokens override profile settings.
The credential resolution chain in src/anthropic/lib/credentials/_chain.py implements steps 2–5 of the precedence spec; step 1 (the explicit credentials= argument) is handled at the client constructor level, above this function.[1] default_credentials() in _chain.py returns None when no credential source matches, so the client falls back to its normal "no auth configured" error.[1]
Step 2a of default_credentials() returns None when ANTHROPIC_API_KEY is set, leaving the base client to handle the X-Api-Key header path directly — API keys are not Bearer tokens and cannot flow through the credential chain.[1] Step 2b wraps ANTHROPIC_AUTH_TOKEN in a StaticToken provider and returns it as a CredentialResult, taking the Bearer token path.[1] Step 3 triggers when ANTHROPIC_PROFILE, ANTHROPIC_CONFIG_DIR, or an active_config pointer file is present; failures at this step propagate rather than being swallowed, because a user who explicitly names a profile expects a broken config to surface.[1] Step 4 (workload identity federation via env vars) sits between explicit profile selection (step 3) and the fallback on-disk profile (step 5), so a machine with WIF env vars set uses WIF even if a leftover default profile exists on disk.[1] Step 5 (fallback active profile from disk) swallows AnthropicError exceptions and returns None, so a corrupt auto-discovered config file does not break an otherwise-explicit api_key= path.[1]
Workload identity federation via env vars requires the trio ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID, and either ANTHROPIC_IDENTITY_TOKEN or ANTHROPIC_IDENTITY_TOKEN_FILE to all be present; missing any of these causes _build_federation_result() to return None.[1] When ANTHROPIC_IDENTITY_TOKEN is used (not a file), _build_federation_result() reads the env var on every call rather than capturing it at construction time, so a rotated value is picked up at the next token exchange.[1] ANTHROPIC_WORKSPACE_ID is coerced from empty-string to None when building a WorkloadIdentityCredentials from env vars, so a defaulted-but-empty CI variable does not put "workspace_id": "" on the wire.[1] Workload identity federation (WIF) is an authentication mechanism that lets a workload (e.g., a CI job or cloud VM) prove its identity using a short-lived token issued by a trusted identity provider, exchanging it for Anthropic credentials without embedding long-lived secrets.
The credential provider module src/anthropic/lib/credentials/_providers.py exports StaticToken, EnvToken, CredentialsFile, InMemoryConfig, and IdentityTokenFile as its public API.[2] StaticToken is an AccessTokenProvider that always returns the same fixed token with no expiry; the force_refresh argument is accepted but ignored because there is no provider-side cache to bypass.[2] EnvToken reads ANTHROPIC_AUTH_TOKEN (or a configured env var) at every call and raises AnthropicError if the variable is unset.[2]
CredentialsFile dispatches on the authentication.type discriminator field: "oidc_federation" triggers OIDC workload identity federation via a WorkloadIdentityCredentials delegate; "user_oauth" handles interactive PKCE login tokens with optional refresh-token grant rotation.[2] For user_oauth profiles without a client_id, CredentialsFile treats the credentials file as externally rotated — it re-reads the file on every invocation and returns whatever access_token is present, with no refresh grant attempted. This is the sidecar/daemon pattern.[2] CredentialsFile resolves its profile name at construction time in this order: explicit profile argument → ANTHROPIC_PROFILE env var → active_config pointer file → "default".[2] Config loading is lazy: CredentialsFile._load_config() populates the config on first call, keeping construction cheap and exception-free so the credential chain can construct the provider optimistically after an existence check.[2] CredentialsFile.extra_headers() returns an anthropic-workspace-id header derived from the config file, but only for non-federation profiles; for oidc_federation profiles the workspace ID is sent in the jwt-bearer exchange body instead, because the minted token is already workspace-scoped.[2] _fill_missing_from_env() in _providers.py fills unset profile config fields from ANTHROPIC_* env vars, treating the profile file as authoritative; empty-string env values are treated as unset.[2] For oidc_federation profiles, _fill_missing_from_env() synthesizes an identity_token object {"source": "file", "path": <value>} from ANTHROPIC_IDENTITY_TOKEN_FILE when the auth block has no identity_token key.[2]
CredentialsFile.resolved_base_url returns None when the profile config has no base_url key, so a profile that omits base_url never overrides an explicit client setting.[2] CredentialsFile.bind_base_url() establishes base_url precedence as: profile config file field → bound value → hard-coded default. The owning client binds exactly once at construction; sharing one instance across clients with different base_url values is unsupported.[2] URL validation in bind_base_url() runs eagerly at bind time against HTTPS rules rather than being deferred to _load_config(), so a misconfigured base_url fails fast rather than at first token exchange.[2]
Both the config file and credentials file are versioned at "1.0" (CONFIG_FILE_VERSION and CREDENTIALS_FILE_VERSION); an absent version field on read is treated as version 1.[2] The credentials file discriminator constant is "oauth_token" (CREDENTIALS_FILE_TYPE), the only type value in v1; future credential shapes (e.g. private key material) are intended to use their own discriminator values.[2] The expires_at field in the credentials file must be an integer Unix timestamp in seconds; the SDK does not parse ISO 8601 strings and raises AnthropicError with the actionable message to use int(datetime.timestamp()) if the value cannot be coerced.[2]
Sources