Skip to content

Conversation

@giranm
Copy link

@giranm giranm commented Feb 9, 2026

Summary

Implements a comprehensive authentication system with OAuth2 PKCE flow and multi-profile support:

Authentication Features:

  • OAuth2 login with PKCE (Proof Key for Code Exchange) flow using Braintrust MCP endpoints
  • API key authentication fallback for headless/CI environments
  • Automatic token refresh for expired OAuth2 tokens
  • Profile-based credential storage in ~/.config/bt/config.json
  • Support for multiple named profiles with --profile flag (default: "DEFAULT")

Profile Management:

  • Store API URL, access token, refresh token, expiry, org_name, and default project per profile
  • bt auth login - OAuth2 or API key login
  • bt auth token - Display current token and TTL
  • bt auth logout - Remove profile

Credential Resolution:

  • Priority: --api-key flag > profile auth > SDK fallback
  • OAuth2 tokens work without org_name (JWT contains identity)
  • API keys require org_name for x-bt-org-name header
  • Project resolution: --project flag > BRAINTRUST_DEFAULT_PROJECT env > profile.project

Projects API Updates:

  • Conditional org_name handling for OAuth2 vs API key authentication
  • list_projects, create_project, get_project_by_name support both auth methods

Test Coverage:

  • 20 new tests for auth.rs covering PKCE generation, token refresh, OAuth parsing
  • 10 new tests for config.rs covering profile CRUD operations
  • All 51 unit tests passing

Recording

cli_update.mov

Implements a comprehensive authentication system with OAuth2 PKCE flow and multi-profile support:

Authentication Features:
- OAuth2 login with PKCE (Proof Key for Code Exchange) flow using Braintrust MCP endpoints
- API key authentication fallback for headless/CI environments
- Automatic token refresh for expired OAuth2 tokens
- Profile-based credential storage in ~/.config/bt/config.json
- Support for multiple named profiles with --profile flag (default: "DEFAULT")

Profile Management:
- Store API URL, access token, refresh token, expiry, org_name, and default project per profile
- bt auth login - OAuth2 or API key login
- bt auth token - Display current token and TTL
- bt auth logout - Remove profile

Credential Resolution:
- Priority: --api-key flag > profile auth > SDK fallback
- OAuth2 tokens work without org_name (JWT contains identity)
- API keys require org_name for x-bt-org-name header
- Project resolution: --project flag > BRAINTRUST_DEFAULT_PROJECT env > profile.project

Projects API Updates:
- Conditional org_name handling for OAuth2 vs API key authentication
- list_projects, create_project, get_project_by_name support both auth methods

Test Coverage:
- 20 new tests for auth.rs covering PKCE generation, token refresh, OAuth parsing
- 10 new tests for config.rs covering profile CRUD operations
- All 51 unit tests passing

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Comment on lines +23 to +24
// OAuth2 tokens don't need org_name filtering
"/v1/project".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.

they do, actually. in fact, oauth tokens are not scoped to orgs, but api keys are.

in general, this code should be not any different based on what type of token you use.

Comment on lines +14 to +17
/// Auth profile to use
#[arg(long, env = "BRAINTRUST_PROFILE", default_value = "DEFAULT")]
pub profile: String,

Copy link
Contributor

Choose a reason for hiding this comment

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

what is an auth profile in the context of braintrust?

Comment on lines +29 to +45
pub struct LoginArgs {
/// Profile name to use
#[arg(long, default_value = "DEFAULT")]
pub profile: String,

/// API URL (defaults to https://api.braintrust.dev)
#[arg(long)]
pub api_url: Option<String>,

/// Use API key instead of OAuth2 (for headless/CI)
#[arg(long)]
pub api_key: bool,

/// Optional default project for this profile
#[arg(long)]
pub project: Option<String>,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

these arguments should not be duplicated from the base

return Ok(orgs[selection].clone());
}

// Fallback: prompt user for org name with helpful guidance
Copy link
Contributor

Choose a reason for hiding this comment

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

these fallbacks are usually bugs

.context("failed to parse OAuth discovery response")
}

fn generate_code_verifier() -> String {
Copy link
Contributor

Choose a reason for hiding this comment

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

would rather use an oauth library

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