fix: use Bearer auth when key comes from ANTHROPIC_AUTH_TOKEN#196
fix: use Bearer auth when key comes from ANTHROPIC_AUTH_TOKEN#196jamiepine merged 3 commits intospacedriveapp:mainfrom
Conversation
When ANTHROPIC_AUTH_TOKEN is the credential source (instead of ANTHROPIC_API_KEY), proxy endpoints expect Authorization: Bearer rather than x-api-key. This matches Claude Code's behavior. Adds a ProxyBearer auth path that sends Bearer without Claude Code identity headers (user-agent, x-app, oauth beta). The auth source is tracked via use_bearer_auth on ProviderConfig, set automatically when the key originates from ANTHROPIC_AUTH_TOKEN. Fixes the 403 errors reported in spacedriveapp#135 when using corporate proxies.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review infoConfiguration used: Organization UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
WalkthroughAdds a new public boolean field Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/llm/manager.rs (1)
178-189:⚠️ Potential issue | 🟠 MajorClear
use_bearer_authwhen swapping in Anthropic OAuth tokens.When OAuth credentials are present, this branch overwrites
provider.api_keybut retainsuse_bearer_auth. If the static provider was sourced fromANTHROPIC_AUTH_TOKEN, that flag will forceProxyBearerand drop OAuth identity headers/tool normalization. Reset the flag when you replace the key with an OAuth token.Proposed fix
- (Some(mut provider), Some(token)) => { - provider.api_key = token; - Ok(provider) - } + (Some(mut provider), Some(token)) => { + provider.api_key = token; + provider.use_bearer_auth = false; + Ok(provider) + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/llm/manager.rs` around lines 178 - 189, In the (Some(mut provider), Some(token)) branch where you replace provider.api_key with an OAuth token, also clear provider.use_bearer_auth (set it to false) so the swapped-in OAuth identity is used instead of ProxyBearer; ensure ProviderConfig creation for the (None, Some(token)) branch remains with use_bearer_auth = false (ProviderConfig and ApiType::Anthropic are the relevant symbols to check).src/config.rs (1)
2541-2705:⚠️ Potential issue | 🟠 MajorHandle
env:ANTHROPIC_AUTH_TOKENsources in TOML configs.Line 2541 and Line 2668: when TOML uses
anthropic_key = "env:ANTHROPIC_AUTH_TOKEN"or an explicit[llm.provider.*]entry withapi_key = "env:ANTHROPIC_AUTH_TOKEN",use_bearer_authstays false. That yieldsx-api-keyheaders and breaks proxy auth. You should treat env-referenced AUTH_TOKEN as a bearer source in both the shorthand and provider-table paths.💡 Suggested fix
- let toml_llm_anthropic_key_was_none = toml - .llm - .anthropic_key - .as_deref() - .and_then(resolve_env_value) - .is_none(); + let toml_anthropic_key_raw = toml.llm.anthropic_key.as_deref(); + let toml_anthropic_key_is_auth_token = toml_anthropic_key_raw + .and_then(|value| value.strip_prefix("env:")) + .is_some_and(|name| name.eq_ignore_ascii_case("ANTHROPIC_AUTH_TOKEN")); + let toml_llm_anthropic_key_was_none = toml_anthropic_key_raw + .and_then(resolve_env_value) + .is_none(); @@ - let anthropic_from_auth_token = toml_llm_anthropic_key_was_none - && std::env::var("ANTHROPIC_API_KEY").is_err() - && std::env::var("ANTHROPIC_AUTH_TOKEN").is_ok(); + let anthropic_from_auth_token = toml_anthropic_key_is_auth_token + || (toml_llm_anthropic_key_was_none + && std::env::var("ANTHROPIC_API_KEY").is_err() + && std::env::var("ANTHROPIC_AUTH_TOKEN").is_ok()); @@ - Ok(( - provider_id.to_lowercase(), - ProviderConfig { - api_type: config.api_type, - base_url: config.base_url, - api_key, - name: config.name, - use_bearer_auth: false, - }, - )) + let use_bearer_auth = config.api_type == ApiType::Anthropic + && config + .api_key + .strip_prefix("env:") + .is_some_and(|name| name.eq_ignore_ascii_case("ANTHROPIC_AUTH_TOKEN")); + Ok(( + provider_id.to_lowercase(), + ProviderConfig { + api_type: config.api_type, + base_url: config.base_url, + api_key, + name: config.name, + use_bearer_auth, + }, + ))Also applies to: 2668-2680
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/config.rs` around lines 2541 - 2705, The Anthropic auth token sourced via env reference isn't being marked as bearer, causing x-api-key headers; update the logic that builds LlmConfig (the llm.anthropic_key branch and the providers map created in from_toml) to detect when an API key came from the ANTHROPIC_AUTH_TOKEN env var (i.e., when resolve_env_value or the provider config's api_key reference resolves to the ANTHROPIC_AUTH_TOKEN variable) and set ProviderConfig.use_bearer_auth = true for the "anthropic" provider; reuse toml_llm_anthropic_key_was_none and anthropic_from_auth_token checks for the shorthand path and add equivalent detection when mapping toml.llm.providers (inside the providers .map closure) so any provider entry whose resolved api_key originates from ANTHROPIC_AUTH_TOKEN toggles use_bearer_auth.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@src/config.rs`:
- Around line 2541-2705: The Anthropic auth token sourced via env reference
isn't being marked as bearer, causing x-api-key headers; update the logic that
builds LlmConfig (the llm.anthropic_key branch and the providers map created in
from_toml) to detect when an API key came from the ANTHROPIC_AUTH_TOKEN env var
(i.e., when resolve_env_value or the provider config's api_key reference
resolves to the ANTHROPIC_AUTH_TOKEN variable) and set
ProviderConfig.use_bearer_auth = true for the "anthropic" provider; reuse
toml_llm_anthropic_key_was_none and anthropic_from_auth_token checks for the
shorthand path and add equivalent detection when mapping toml.llm.providers
(inside the providers .map closure) so any provider entry whose resolved api_key
originates from ANTHROPIC_AUTH_TOKEN toggles use_bearer_auth.
In `@src/llm/manager.rs`:
- Around line 178-189: In the (Some(mut provider), Some(token)) branch where you
replace provider.api_key with an OAuth token, also clear
provider.use_bearer_auth (set it to false) so the swapped-in OAuth identity is
used instead of ProxyBearer; ensure ProviderConfig creation for the (None,
Some(token)) branch remains with use_bearer_auth = false (ProviderConfig and
ApiType::Anthropic are the relevant symbols to check).
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
src/api/providers.rssrc/config.rssrc/llm/anthropic/auth.rssrc/llm/anthropic/params.rssrc/llm/manager.rssrc/llm/model.rs
| // In from_toml, the key may come from toml config, ANTHROPIC_API_KEY, or | ||
| // ANTHROPIC_AUTH_TOKEN (in that priority order). We only set use_bearer_auth | ||
| // if AUTH_TOKEN was the actual source. | ||
| let anthropic_from_auth_token = toml_llm_anthropic_key_was_none |
There was a problem hiding this comment.
Looks like anthropic_from_auth_token only flips on when [llm].anthropic_key is absent and the env fallback picks ANTHROPIC_AUTH_TOKEN. If TOML sets anthropic_key = "env:ANTHROPIC_AUTH_TOKEN", this stays false and we’d still send x-api-key.
| let anthropic_from_auth_token = toml_llm_anthropic_key_was_none | |
| let anthropic_from_auth_token = | |
| matches!(toml.llm.anthropic_key.as_deref(), Some("env:ANTHROPIC_AUTH_TOKEN")) | |
| || (toml_llm_anthropic_key_was_none | |
| && std::env::var("ANTHROPIC_API_KEY").is_err() | |
| && std::env::var("ANTHROPIC_AUTH_TOKEN").is_ok()); |
| let (request, _) = build_request_with_bearer("my-proxy-token", false, true); | ||
| assert!(request.headers().get("x-app").is_none()); | ||
| // Should not have Claude Code user-agent | ||
| let ua = request.headers().get("user-agent").map(|v| v.to_str().unwrap().to_string()); |
There was a problem hiding this comment.
Minor test hardening: avoid to_str().unwrap() here (panics on non-UTF8 header values).
| let ua = request.headers().get("user-agent").map(|v| v.to_str().unwrap().to_string()); | |
| let ua = request | |
| .headers() | |
| .get("user-agent") | |
| .and_then(|value| value.to_str().ok()); | |
| assert!(ua.map_or(true, |ua| !ua.contains("claude-code"))); |
Problem
When
ANTHROPIC_AUTH_TOKENis used (instead ofANTHROPIC_API_KEY), the request still sends the key via thex-api-keyheader. Most proxy endpoints expectAuthorization: Bearer <token>instead — this is how Claude Code handles it whenANTHROPIC_AUTH_TOKENis set.This causes 403 errors when using corporate/custom Anthropic-compatible proxies (LiteLLM, Azure AI Gateway, etc.), as reported in #135.
Solution
Adds a
ProxyBearerauth path variant alongside the existingApiKeyandOAuthTokenpaths:ApiKey→x-api-key: <key>(native Anthropic, unchanged)OAuthToken→Authorization: Bearer+ Claude Code identity headers (unchanged)ProxyBearer(new) →Authorization: Beareronly, no identity headersAuth source tracking via
use_bearer_authonProviderConfig, set automatically when the key originates fromANTHROPIC_AUTH_TOKENin bothload_from_envandfrom_tomlcode paths.This aligns with Claude Code's third-party integrations behavior.
Changes
src/llm/anthropic/auth.rs—ProxyBearervariant, updateddetect_auth_path(token, force_bearer), updatedapply_auth_headers, new testssrc/config.rs—use_bearer_authfield onProviderConfig, auth source detection in both loading pathssrc/llm/anthropic/params.rs— threadsforce_bearerthroughbuild_anthropic_requestsrc/llm/model.rs— passesuse_bearer_authfrom provider configsrc/api/providers.rs,src/llm/manager.rs—use_bearer_auth: falseon all other provider constructionsTesting
ProxyBearerpath (bearer header, no identity headers, correct beta flags)detect_auth_pathsignatureANTHROPIC_API_KEYusersCloses the auth header issue reported in #135 (comment)
Note
Automated Summary
This PR implements Bearer token authentication for proxy-compatible Anthropic endpoints when
ANTHROPIC_AUTH_TOKENis used. The core changes add a newProxyBearerauth path that sendsAuthorization: Bearerheaders without Claude Code identity headers, allowing seamless integration with corporate proxies and Anthropic-compatible services. Configuration detection automatically sets theuse_bearer_authflag when the token originates from theANTHROPIC_AUTH_TOKENenvironment variable (prioritizing it overANTHROPIC_API_KEY). The implementation is backward compatible—existing API key users are unaffected—and includes 4 new unit tests validating the Bearer auth flow.Written by Tembo for commit 313c6f6