Skip to content

Comments

fix: use Bearer auth when key comes from ANTHROPIC_AUTH_TOKEN#196

Merged
jamiepine merged 3 commits intospacedriveapp:mainfrom
worldofgeese:fix/auth-token-bearer
Feb 25, 2026
Merged

fix: use Bearer auth when key comes from ANTHROPIC_AUTH_TOKEN#196
jamiepine merged 3 commits intospacedriveapp:mainfrom
worldofgeese:fix/auth-token-bearer

Conversation

@worldofgeese
Copy link

@worldofgeese worldofgeese commented Feb 24, 2026

Problem

When ANTHROPIC_AUTH_TOKEN is used (instead of ANTHROPIC_API_KEY), the request still sends the key via the x-api-key header. Most proxy endpoints expect Authorization: Bearer <token> instead — this is how Claude Code handles it when ANTHROPIC_AUTH_TOKEN is set.

This causes 403 errors when using corporate/custom Anthropic-compatible proxies (LiteLLM, Azure AI Gateway, etc.), as reported in #135.

Solution

Adds a ProxyBearer auth path variant alongside the existing ApiKey and OAuthToken paths:

  • ApiKeyx-api-key: <key> (native Anthropic, unchanged)
  • OAuthTokenAuthorization: Bearer + Claude Code identity headers (unchanged)
  • ProxyBearer (new) → Authorization: Bearer only, no identity headers

Auth source tracking via use_bearer_auth on ProviderConfig, set automatically when the key originates from ANTHROPIC_AUTH_TOKEN in both load_from_env and from_toml code paths.

This aligns with Claude Code's third-party integrations behavior.

Changes

  • src/llm/anthropic/auth.rsProxyBearer variant, updated detect_auth_path(token, force_bearer), updated apply_auth_headers, new tests
  • src/config.rsuse_bearer_auth field on ProviderConfig, auth source detection in both loading paths
  • src/llm/anthropic/params.rs — threads force_bearer through build_anthropic_request
  • src/llm/model.rs — passes use_bearer_auth from provider config
  • src/api/providers.rs, src/llm/manager.rsuse_bearer_auth: false on all other provider constructions

Testing

  • Added 4 new unit tests for ProxyBearer path (bearer header, no identity headers, correct beta flags)
  • Updated existing tests for new detect_auth_path signature
  • No behavioral change for existing ANTHROPIC_API_KEY users

Closes 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_TOKEN is used. The core changes add a new ProxyBearer auth path that sends Authorization: Bearer headers without Claude Code identity headers, allowing seamless integration with corporate proxies and Anthropic-compatible services. Configuration detection automatically sets the use_bearer_auth flag when the token originates from the ANTHROPIC_AUTH_TOKEN environment variable (prioritizing it over ANTHROPIC_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

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.
@coderabbitai
Copy link

coderabbitai bot commented Feb 24, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 78e0418 and fc2123d.

📒 Files selected for processing (1)
  • src/llm/model.rs

Walkthrough

Adds a new public boolean field use_bearer_auth to ProviderConfig, detects proxy-style Anthropic auth from env/TOML sources, and threads this flag through config loading, auth selection, and Anthropic request construction to enable Proxy Bearer authentication.

Changes

Cohort / File(s) Summary
Configuration & Provider Setup
src/config.rs, src/api/providers.rs
Add pub use_bearer_auth: bool to ProviderConfig; detect proxy-style Anthropic auth in env/TOML loaders and set the flag for Anthropic; initialize flag (default false) at provider construction sites; include field in Debug output.
Anthropic Authentication Handler
src/llm/anthropic/auth.rs
Add AnthropicAuthPath::ProxyBearer. Change detect_auth_path(token, force_bearer) and apply_auth_headers(..., force_bearer) signatures. Implement ProxyBearer header handling (Bearer Authorization, omit Claude identity headers, streaming beta handling); update/add tests for force_bearer behavior.
Request Building & Orchestration
src/llm/anthropic/params.rs, src/llm/model.rs, src/llm/manager.rs
Extend build_anthropic_request(...) signature with force_bearer/use_bearer_auth bool and propagate it to auth functions. Thread provider use_bearer_auth into request construction; add explicit use_bearer_auth: false initializers in manager where applicable.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: enabling Bearer authentication for tokens from ANTHROPIC_AUTH_TOKEN, which is the core purpose of this PR.
Description check ✅ Passed The description provides comprehensive context about the problem, solution, changes made, and testing approach, all directly related to the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🟠 Major

Clear use_bearer_auth when swapping in Anthropic OAuth tokens.

When OAuth credentials are present, this branch overwrites provider.api_key but retains use_bearer_auth. If the static provider was sourced from ANTHROPIC_AUTH_TOKEN, that flag will force ProxyBearer and 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 | 🟠 Major

Handle env:ANTHROPIC_AUTH_TOKEN sources in TOML configs.

Line 2541 and Line 2668: when TOML uses anthropic_key = "env:ANTHROPIC_AUTH_TOKEN" or an explicit [llm.provider.*] entry with api_key = "env:ANTHROPIC_AUTH_TOKEN", use_bearer_auth stays false. That yields x-api-key headers 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

📥 Commits

Reviewing files that changed from the base of the PR and between 7170ca0 and 313c6f6.

📒 Files selected for processing (6)
  • src/api/providers.rs
  • src/config.rs
  • src/llm/anthropic/auth.rs
  • src/llm/anthropic/params.rs
  • src/llm/manager.rs
  • src/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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor test hardening: avoid to_str().unwrap() here (panics on non-UTF8 header values).

Suggested change
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")));

@jamiepine jamiepine merged commit f98c30e into spacedriveapp:main Feb 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants