> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/block/goose/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth Providers

> Implement providers with OAuth authentication for Goose

Some LLM providers use OAuth for authentication instead of API keys. Goose supports OAuth device code flow for secure, user-friendly authentication.

## OAuth in Goose

Goose's OAuth implementation (`crates/goose/src/providers/oauth.rs`) supports:

* **Device Code Flow** - User authenticates via browser
* **Token Caching** - Tokens stored securely, reused across sessions
* **Automatic Refresh** - Expired tokens refreshed automatically
* **PKCE** - Secure authorization without client secrets

## When to Use OAuth

Use OAuth authentication when:

* Provider requires OAuth (e.g., Databricks)
* You want SSO integration
* API keys aren't available or practical
* Enterprise authentication is needed

## OAuth Flow Overview

```
1. User initiates configuration
   ↓
2. Goose requests authorization
   ↓
3. Browser opens to provider's auth page
   ↓
4. User logs in and approves
   ↓
5. Provider redirects with auth code
   ↓
6. Goose exchanges code for tokens
   ↓
7. Tokens cached for future use
```

## Implementing OAuth Providers

### 1. Define Provider with OAuth ConfigKey

In your `ProviderDef` implementation:

```rust theme={null}
use goose::providers::base::{ProviderDef, ProviderMetadata, ConfigKey};

impl ProviderDef for MyOAuthProvider {
    type Provider = Self;
    
    fn metadata() -> ProviderMetadata {
        ProviderMetadata::new(
            "myoauthprovider",
            "My OAuth Provider",
            "Provider using OAuth authentication",
            "default-model",
            vec!["model-1", "model-2"],
            "https://provider.com/models",
            vec![
                // Mark this key as using OAuth
                ConfigKey::new_oauth(
                    "ACCESS_TOKEN",
                    true,   // required
                    true,   // secret
                    None,   // no default
                    true,   // primary
                ),
                // Other config keys
                ConfigKey::new(
                    "HOST",
                    true,
                    false,
                    Some("https://provider.com"),
                    false,
                ),
            ],
        )
    }
    
    // ... other methods
}
```

### 2. Implement configure\_oauth Method

```rust theme={null}
use goose::providers::base::Provider;
use goose::providers::oauth::get_oauth_token_async;

#[async_trait]
impl Provider for MyOAuthProvider {
    async fn configure_oauth(&self) -> Result<(), ProviderError> {
        // Get OAuth token
        let token = get_oauth_token_async(
            &self.host,                    // Authorization server
            &self.client_id,               // OAuth client ID
            "http://localhost:8080",       // Redirect URI
            &[                             // Requested scopes
                "api.read".to_string(),
                "api.write".to_string(),
            ],
        )
        .await
        .map_err(|e| ProviderError::ConfigError(
            format!("OAuth authentication failed: {}", e)
        ))?;
        
        // Store token securely (in keychain)
        self.store_token(&token)?;
        
        println!("OAuth authentication successful!");
        Ok(())
    }
    
    // ... other Provider methods
}
```

### 3. Use Token in Requests

```rust theme={null}
impl MyOAuthProvider {
    async fn make_api_request(&self, request: Request) -> Result<Response> {
        // Get token (from cache or refresh if needed)
        let token = self.get_access_token().await?;
        
        let response = self.client
            .post(&self.api_endpoint)
            .header("Authorization", format!("Bearer {}", token))
            .json(&request)
            .send()
            .await?;
        
        Ok(response)
    }
    
    async fn get_access_token(&self) -> Result<String> {
        // Load from cache or refresh
        // The oauth module handles this automatically
        let token = get_oauth_token_async(
            &self.host,
            &self.client_id,
            "http://localhost:8080",
            &self.scopes,
        ).await?;
        
        Ok(token)
    }
}
```

## OAuth Token Management

### Token Storage

Tokens are stored in:

```
~/.config/goose/provider/oauth/
  └── {hash}.json
```

The hash is based on:

* Host URL
* Client ID
* Requested scopes

This ensures tokens are isolated per configuration.

### Token Structure

```rust theme={null}
struct TokenData {
    /// Access token for API requests
    access_token: String,
    
    /// Refresh token for obtaining new access tokens
    refresh_token: Option<String>,
    
    /// When the access token expires
    expires_at: Option<DateTime<Utc>>,
}
```

### Automatic Refresh

The OAuth module automatically:

1. Checks if cached token exists
2. Validates expiration time
3. Refreshes if expired using refresh token
4. Falls back to full OAuth flow if refresh fails

```rust theme={null}
// This automatically handles refresh
let token = get_oauth_token_async(
    host,
    client_id,
    redirect_url,
    scopes,
).await?;
```

## Complete Example: Databricks Provider

Databricks uses OAuth for authentication:

```rust theme={null}
use goose::providers::base::*;
use goose::providers::oauth::get_oauth_token_async;
use async_trait::async_trait;

pub struct DatabricksProvider {
    host: String,
    client_id: String,
    scopes: Vec<String>,
    model_config: ModelConfig,
    client: reqwest::Client,
}

impl DatabricksProvider {
    const DEFAULT_CLIENT_ID: &'static str = "databricks-cli";
    const DEFAULT_SCOPES: &[&'static str] = &[
        "all-apis",
        "offline_access",
    ];
    
    pub fn new(
        host: String,
        model_config: ModelConfig,
    ) -> Self {
        Self {
            host,
            client_id: Self::DEFAULT_CLIENT_ID.to_string(),
            scopes: Self::DEFAULT_SCOPES
                .iter()
                .map(|s| s.to_string())
                .collect(),
            model_config,
            client: reqwest::Client::new(),
        }
    }
    
    async fn get_token(&self) -> Result<String, ProviderError> {
        get_oauth_token_async(
            &self.host,
            &self.client_id,
            "http://localhost:8020",
            &self.scopes,
        )
        .await
        .map_err(|e| ProviderError::ConfigError(
            format!("Failed to get OAuth token: {}", e)
        ))
    }
}

#[async_trait]
impl Provider for DatabricksProvider {
    fn get_name(&self) -> &str {
        "databricks"
    }
    
    fn get_model_config(&self) -> ModelConfig {
        self.model_config.clone()
    }
    
    async fn configure_oauth(&self) -> Result<(), ProviderError> {
        println!("Starting Databricks OAuth authentication...");
        println!("A browser window will open for authentication.");
        
        let token = self.get_token().await?;
        
        println!("\nAuthentication successful!");
        println!("Token cached for future use.");
        
        Ok(())
    }
    
    async fn stream(
        &self,
        model_config: &ModelConfig,
        session_id: &str,
        system: &str,
        messages: &[Message],
        tools: &[Tool],
    ) -> Result<MessageStream, ProviderError> {
        let token = self.get_token().await?;
        
        let request = self.build_request(
            model_config,
            system,
            messages,
            tools,
        )?;
        
        let response = self.client
            .post(&format!(
                "{}/serving-endpoints/{}/invocations",
                self.host,
                model_config.model_name
            ))
            .header("Authorization", format!("Bearer {}", token))
            .json(&request)
            .send()
            .await
            .map_err(|e| ProviderError::RequestFailed(
                format!("Request failed: {}", e)
            ))?;
        
        self.process_stream(response).await
    }
}

impl ProviderDef for DatabricksProvider {
    type Provider = Self;
    
    fn metadata() -> ProviderMetadata {
        ProviderMetadata::new(
            "databricks",
            "Databricks",
            "Databricks Model Serving with OAuth",
            "databricks-meta-llama-3-1-70b-instruct",
            vec![
                "databricks-meta-llama-3-1-70b-instruct",
                "databricks-meta-llama-3-1-405b-instruct",
            ],
            "https://docs.databricks.com/en/machine-learning/foundation-models/",
            vec![
                ConfigKey::new(
                    "HOST",
                    true,
                    false,
                    None,
                    true,
                ),
                ConfigKey::new_oauth(
                    "ACCESS_TOKEN",
                    true,
                    true,
                    None,
                    true,
                ),
            ],
        )
    }
    
    fn from_env(
        model_config: ModelConfig,
        _extensions: Vec<ExtensionConfig>,
    ) -> BoxFuture<'static, anyhow::Result<Self::Provider>> {
        Box::pin(async move {
            let host = std::env::var("DATABRICKS_HOST")
                .map_err(|_| anyhow::anyhow!(
                    "DATABRICKS_HOST environment variable not set"
                ))?;
            
            Ok(Self::new(host, model_config))
        })
    }
}
```

## OAuth Configuration UI

When a user configures an OAuth provider:

```bash theme={null}
goose configure --provider databricks
```

Goose:

1. Detects OAuth requirement from `ConfigKey::new_oauth()`
2. Calls `provider.configure_oauth()`
3. Opens browser for authentication
4. Displays success message
5. Caches tokens for future use

## Testing OAuth Providers

### Manual Testing

```bash theme={null}
# Set provider host
export DATABRICKS_HOST=https://your-workspace.cloud.databricks.com

# Configure (triggers OAuth flow)
goose configure --provider databricks

# Test API call
goose session
> Hello, test the connection
```

### Integration Tests

Skip OAuth in tests by mocking:

```rust theme={null}
#[tokio::test]
async fn test_databricks_provider() {
    // Mock token retrieval
    let provider = DatabricksProvider::new_with_token(
        "https://test.databricks.com".to_string(),
        "test-token".to_string(),
        ModelConfig::new_or_fail("test-model"),
    );
    
    // Test API calls with mocked token
    let result = provider.complete(
        &provider.get_model_config(),
        "test-session",
        "You are helpful",
        &[Message::user().with_text("Hello")],
        &[],
    ).await;
    
    assert!(result.is_ok());
}
```

## Security Considerations

### PKCE (Proof Key for Code Exchange)

Goose uses PKCE for secure OAuth:

```rust theme={null}
// Generate code verifier
let verifier = nanoid::nanoid!(64);

// Create code challenge
let challenge = {
    let digest = sha2::Sha256::digest(verifier.as_bytes());
    base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(digest)
};

// Include in authorization request
params.push(("code_challenge", &challenge));
params.push(("code_challenge_method", "S256"));
```

### Token Storage

Tokens are stored:

* In user's config directory
* With restrictive file permissions
* Hashed filename for privacy
* Never logged or printed

### State Parameter

CSRF protection using state parameter:

```rust theme={null}
let state = nanoid::nanoid!(16);

// Sent in auth request
params.push(("state", &state));

// Validated in callback
if received_state != state {
    return Err("State mismatch - possible CSRF attack");
}
```

## Advanced OAuth Features

### Custom Scopes

```rust theme={null}
impl MyProvider {
    fn get_required_scopes(&self) -> Vec<String> {
        vec![
            "read:models".to_string(),
            "write:completions".to_string(),
            "offline_access".to_string(),  // For refresh tokens
        ]
    }
}
```

### Custom Client ID

```rust theme={null}
impl ProviderDef for MyProvider {
    fn from_env(
        model_config: ModelConfig,
        _extensions: Vec<ExtensionConfig>,
    ) -> BoxFuture<'static, anyhow::Result<Self::Provider>> {
        Box::pin(async move {
            let client_id = std::env::var("MYPROVIDER_CLIENT_ID")
                .unwrap_or_else(|_| "default-client-id".to_string());
            
            Ok(Self::new(client_id, model_config))
        })
    }
}
```

### Custom Redirect Port

```rust theme={null}
let token = get_oauth_token_async(
    host,
    client_id,
    "http://localhost:9999",  // Custom port
    scopes,
).await?;
```

Note: Port 0 allows OS to assign an available port.

## Troubleshooting

### Browser Doesn't Open

If browser fails to open automatically:

```
Please open this URL in your browser:
https://provider.com/oauth/authorize?client_id=...
```

User can copy/paste URL manually.

### Token Refresh Fails

If refresh fails:

* Falls back to full OAuth flow
* User re-authenticates in browser
* New tokens cached

### Port Already in Use

If redirect port is occupied:

* Use port 0 for auto-assignment
* Or specify different port

### Token Expiration

Tokens automatically refresh before expiration:

```rust theme={null}
// Check expiration
if let Some(expires_at) = token.expires_at {
    if expires_at > Utc::now() {
        // Token still valid
        return Ok(token.access_token);
    }
}

// Token expired, refresh it
let new_token = refresh_token(&token.refresh_token).await?;
```

## OAuth vs API Keys

**Use OAuth when:**

* Provider requires it
* Enterprise SSO needed
* Temporary access desired
* Fine-grained permissions required

**Use API Keys when:**

* Simpler for users
* Provider supports them
* No enterprise requirements
* Faster setup needed

## Next Steps

* Implement [Custom Providers](/development/provider-interface)
* Learn about [Declarative Providers](/development/declarative-providers)
* See OAuth implementation in `crates/goose/src/providers/oauth.rs`
