fix: use Bearer auth when key comes from ANTHROPIC_AUTH_TOKEN#196
fix: use Bearer auth when key comes from ANTHROPIC_AUTH_TOKEN#196worldofgeese wants to merge 2 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.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review infoConfiguration used: Organization UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
WalkthroughAdds a new public boolean field Changes
Sequence DiagramsequenceDiagram
participant Config as Config Loader
participant Provider as ProviderConfig
participant Model as SpacebotModel
participant Builder as Request Builder
participant Auth as Auth Handler
Config->>Config: load_from_env / from_toml
Config->>Provider: set use_bearer_auth (anthropic detection)
Model->>Provider: read provider_config.use_bearer_auth
Model->>Builder: build_anthropic_request(..., force_bearer)
Builder->>Auth: detect_auth_path(token, force_bearer)
alt ProxyBearer chosen
Auth->>Auth: return ProxyBearer
Auth->>Builder: apply Bearer Authorization header (no Claude identity)
else Standard path
Auth->>Auth: return OAuth/APIKey variant
Auth->>Builder: apply standard Anthropic headers (with identity)
end
Builder->>Model: return configured RequestBuilder
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 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