Skip to content

Commit b2ef783

Browse files
BunsDevNova
andauthored
fix(auth): support first-party Anthropic OAuth (#45) thanks @BunsDev
Restores Anthropic OAuth/PKCE behind COVEN_CODE_ANTHROPIC_OAUTH_CLIENT_ID so Coven Code does not become API-key-only while still blocking borrowed Claude Code OAuth tokens. Verified locally after rebasing on current main: - rustfmt --edition 2021 --check crates/cli/src/oauth_flow.rs - cargo test -p claurst-core test_oauth_bearer_token_requires_configured_client_id -- --nocapture - cargo test -p claurst --no-default-features --bin coven-code oauth_flow -- --nocapture - cargo check -p claurst-core - cargo check -p claurst-api - cargo check -p claurst --no-default-features - git diff --check origin/main...HEAD - Runtime auth status smoke: old borrowed Claude.ai bearer token is disabled / loggedIn false - Runtime auth status smoke: configured first-party bearer token is accepted / loggedIn true Co-authored-by: Nova <nova@openclaw.ai>
1 parent 399f8e9 commit b2ef783

9 files changed

Lines changed: 537 additions & 61 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,10 @@ export ANTHROPIC_API_KEY=<your-key>
103103
coven-code
104104
```
105105

106-
Or log in via OAuth (Anthropic accounts):
106+
Or log in via OAuth after configuring a Coven Code OAuth client:
107107

108108
```bash
109+
export COVEN_CODE_ANTHROPIC_OAUTH_CLIENT_ID=<registered-client-id>
109110
coven-code auth login
110111
```
111112

docs/auth.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -94,16 +94,15 @@ coven-code --api-key "sk-ant-api03-..." "your prompt"
9494
Coven Code supports an OAuth 2.0 PKCE flow that authenticates through either
9595
the Anthropic Console or Claude.ai in your browser.
9696

97-
> **Important:** The OAuth client IDs in Coven Code are registered to Anthropic's
98-
> official Claude Code CLI application. Anthropic's authorization server may
99-
> reject or misattribute OAuth requests originating from Coven Code. The API key
100-
> method is the recommended path for Coven Code users.
101-
>
102-
> If OAuth login is attempted and fails, use Method 1 (API key) instead.
97+
> **Important:** Coven Code must not reuse Claude Code's OAuth client ID.
98+
> Anthropic OAuth requires a client ID registered for Coven Code and supplied
99+
> through `COVEN_CODE_ANTHROPIC_OAUTH_CLIENT_ID`. Until that first-party client
100+
> is configured, use Method 1 (API key).
103101
104-
### Claude.ai flow (default)
102+
### Claude.ai flow
105103

106104
```bash
105+
export COVEN_CODE_ANTHROPIC_OAUTH_CLIENT_ID=<registered-client-id>
107106
coven-code auth login
108107
```
109108

@@ -125,6 +124,7 @@ API calls.
125124
### Console flow (creates an API key)
126125

127126
```bash
127+
export COVEN_CODE_ANTHROPIC_OAUTH_CLIENT_ID=<registered-client-id>
128128
coven-code auth login --console
129129
```
130130

@@ -204,8 +204,8 @@ providers:
204204

205205
```bash
206206
# Add accounts (each login becomes its own profile)
207-
coven-code auth login # Claude.ai (default)
208-
coven-code auth login --console # Console / API-key flow
207+
coven-code auth login # Claude.ai, requires COVEN_CODE_ANTHROPIC_OAUTH_CLIENT_ID
208+
coven-code auth login --console # Console / API-key flow, requires COVEN_CODE_ANTHROPIC_OAUTH_CLIENT_ID
209209
coven-code auth login --label work # name the profile
210210
coven-code codex login # ChatGPT/Codex OAuth
211211
coven-code codex login --label personal

src-rust/crates/api/src/lib.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -549,9 +549,9 @@ pub mod client {
549549
model
550550
)
551551
} else {
552-
"Set ANTHROPIC_API_KEY, or use --provider to select a different provider \
553-
(e.g. --provider openai). Anthropic OAuth login is disabled until Coven Code \
554-
has its own OAuth client.".to_string()
552+
"Set ANTHROPIC_API_KEY, configure COVEN_CODE_ANTHROPIC_OAUTH_CLIENT_ID \
553+
and run `coven-code auth login`, or use --provider to select a different \
554+
provider (e.g. --provider openai).".to_string()
555555
};
556556
return Err(ClaudeError::Auth(
557557
format!("No API key for the selected model. {}", hint)
@@ -653,9 +653,9 @@ pub mod client {
653653
} else if model.starts_with("llama") {
654654
format!("Model '{}' looks like a Llama model. Use `--provider groq` or `--provider ollama` for local.", model)
655655
} else {
656-
"Set ANTHROPIC_API_KEY, or use --provider to select a different provider \
657-
(e.g. --provider openai). Anthropic OAuth login is disabled until Coven Code \
658-
has its own OAuth client.".to_string()
656+
"Set ANTHROPIC_API_KEY, configure COVEN_CODE_ANTHROPIC_OAUTH_CLIENT_ID \
657+
and run `coven-code auth login`, or use --provider to select a different \
658+
provider (e.g. --provider openai).".to_string()
659659
};
660660
return Err(ClaudeError::Auth(
661661
format!("No API key for the selected model. {}", hint)

src-rust/crates/cli/src/main.rs

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -864,7 +864,7 @@ async fn main() -> anyhow::Result<()> {
864864
- Set GOOGLE_API_KEY for Google Gemini\n\
865865
- Set GROQ_API_KEY for Groq (fast, free tier available)\n\
866866
- Run `coven-code --provider ollama` for local models (no key needed)\n\
867-
- Anthropic OAuth login is disabled until Coven Code has its own OAuth client"
867+
- Configure COVEN_CODE_ANTHROPIC_OAUTH_CLIENT_ID, then run `coven-code auth login` for first-party Anthropic OAuth"
868868
);
869869
} else {
870870
(String::new(), false)
@@ -3840,13 +3840,11 @@ async fn run_interactive(
38403840
}
38413841
"anthropic" => {
38423842
let tx2 = device_auth_tx.clone();
3843-
// Anthropic OAuth requires a registered application.
3844-
// Coven Code does not have its own registered OAuth app with Anthropic.
3845-
// Users should use an API key from console.anthropic.com instead.
3843+
// Anthropic OAuth requires a registered Coven Code application.
38463844
tokio::spawn(async move {
38473845
let _ = tx2.send(DeviceAuthEvent::Error(
3848-
"Anthropic OAuth requires a registered application.\n\
3849-
Use an API key instead: console.anthropic.com/settings/keys".to_string()
3846+
"Anthropic OAuth requires COVEN_CODE_ANTHROPIC_OAUTH_CLIENT_ID.\n\
3847+
Configure a Coven Code OAuth client, or use an API key for now: console.anthropic.com/settings/keys".to_string()
38503848
)).await;
38513849
});
38523850
}
@@ -4238,7 +4236,7 @@ async fn run_interactive(
42384236
// Called before Cli::parse() so it doesn't conflict with positional `prompt`.
42394237
//
42404238
// Usage:
4241-
// claude auth login [--console] — Anthropic OAuth is disabled until Coven Code has its own client
4239+
// claude auth login [--console] — Anthropic OAuth with a configured Coven Code client
42424240
// claude auth logout — Clear stored credentials
42434241
// claude auth status [--json] — Show authentication status
42444242

@@ -4325,7 +4323,7 @@ async fn handle_auth_command(args: &[String]) -> anyhow::Result<()> {
43254323

43264324
fn print_auth_usage() {
43274325
eprintln!("Usage: coven-code auth <subcommand>");
4328-
eprintln!(" login [--console] [--label <name>] Authenticate (claude.ai by default)");
4326+
eprintln!(" login [--console] [--label <name>] Authenticate with a configured Coven Code OAuth client");
43294327
eprintln!(" logout Remove the active account's credentials");
43304328
eprintln!(" status [--json] Show authentication status");
43314329
eprintln!(" list List all stored Anthropic accounts");
@@ -4651,12 +4649,12 @@ async fn auth_status(json_output: bool) {
46514649
.filter(|tokens| !tokens.uses_bearer_auth() && tokens.api_key.is_some())
46524650
.map(|_| "/login managed key".to_string())
46534651
});
4654-
let usable_oauth_tokens = oauth_tokens
4655-
.as_ref()
4656-
.filter(|tokens| !tokens.uses_bearer_auth());
4652+
let usable_oauth_tokens = oauth_tokens.as_ref().filter(|tokens| {
4653+
!tokens.uses_bearer_auth() || tokens.uses_configured_oauth_client()
4654+
});
46574655
let disabled_bearer_token = oauth_tokens
46584656
.as_ref()
4659-
.is_some_and(|tokens| tokens.uses_bearer_auth());
4657+
.is_some_and(|tokens| tokens.uses_bearer_auth() && !tokens.uses_configured_oauth_client());
46604658
let token_source = usable_oauth_tokens.map(|tokens| {
46614659
if tokens.uses_bearer_auth() {
46624660
"claude.ai".to_string()
@@ -4739,9 +4737,9 @@ async fn auth_status(json_output: bool) {
47394737
if !logged_in {
47404738
let hint = if active_provider == "anthropic" {
47414739
if disabled_bearer_token {
4742-
"Stored claude.ai OAuth tokens are disabled; set ANTHROPIC_API_KEY.".to_string()
4740+
"Stored claude.ai OAuth tokens were minted by an unsupported client; configure COVEN_CODE_ANTHROPIC_OAUTH_CLIENT_ID and run `coven-code auth login`, or set ANTHROPIC_API_KEY for now.".to_string()
47434741
} else {
4744-
"Set ANTHROPIC_API_KEY.".to_string()
4742+
"Set ANTHROPIC_API_KEY, or configure COVEN_CODE_ANTHROPIC_OAUTH_CLIENT_ID and run `coven-code auth login`.".to_string()
47454743
}
47464744
} else if let Some(env_var) =
47474745
claurst_core::config::primary_api_key_env_var_for_provider(active_provider)

0 commit comments

Comments
 (0)