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

# OAuth Flow

> OAuth 2.0 authentication flows for cloud providers

# OAuth Flow

Aurora uses OAuth 2.0 for secure authentication with cloud providers. Each provider implements OAuth differently.

## Supported OAuth Providers

<CardGroup cols={2}>
  <Card title="Google Cloud Platform" icon="google">
    OAuth 2.0 with consent screen
  </Card>

  <Card title="Tailscale" icon="shield">
    OAuth client credentials flow
  </Card>

  <Card title="OVH Cloud" icon="cloud">
    OAuth 2.0 with custom scopes
  </Card>

  <Card title="Microsoft Azure" icon="microsoft">
    Service principal (client credentials)
  </Card>
</CardGroup>

## Google Cloud Platform OAuth

GCP uses standard OAuth 2.0 authorization code flow.

### Flow Diagram

```mermaid theme={null}
sequenceDiagram
    participant User
    participant Frontend
    participant Aurora
    participant Google
    
    User->>Frontend: Click "Connect GCP"
    Frontend->>Aurora: POST /gcp/login {userId}
    Aurora->>Frontend: {login_url}
    Frontend->>User: Redirect to Google
    User->>Google: Grant permissions
    Google->>Aurora: GET /gcp/callback?code=AUTH_CODE&state=USER_ID
    Aurora->>Google: Exchange code for tokens
    Google->>Aurora: {access_token, refresh_token}
    Aurora->>Aurora: Store tokens in Vault
    Aurora->>Aurora: Start post-auth setup (async)
    Aurora->>Frontend: Redirect with task_id
    Frontend->>Aurora: Poll GET /gcp/setup/status/TASK_ID
    Aurora->>Frontend: {state, progress, status}
    Aurora->>Frontend: {state: SUCCESS, complete: true}
```

### 1. Initiate OAuth Flow

**POST /gcp/login**

```json theme={null}
{
  "userId": "user_123"
}
```

**Response:**

```json theme={null}
{
  "login_url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=AURORA_CLIENT_ID&redirect_uri=https://aurora.example.com/gcp/callback&response_type=code&scope=https://www.googleapis.com/auth/cloud-platform&state=user_123&access_type=offline&prompt=consent"
}
```

### 2. User Consent

Redirect user to `login_url`:

* User authenticates with Google
* Reviews requested permissions
* Grants consent
* Google redirects to Aurora callback

### 3. Callback & Token Exchange

**GET /gcp/callback**

Google redirects with:

* `code` - Authorization code
* `state` - User ID (for security)

Aurora:

1. Validates state parameter
2. Exchanges code for tokens
3. Stores tokens in Vault
4. Triggers post-auth setup
5. Redirects to frontend

**Redirect URL:**

```
https://aurora.example.com/chat?login=gcp_setup_pending&task_id=abc123
```

### 4. Post-Auth Setup

Asynchronous setup tasks:

1. Fetch user's GCP projects
2. Enable required APIs
3. Create service accounts
4. Configure IAM permissions
5. Set up billing exports
6. Initialize project metadata
7. Complete setup

**Poll Status:**

```bash theme={null}
GET /gcp/setup/status/abc123
```

**Progress Response:**

```json theme={null}
{
  "state": "PROGRESS",
  "status": "Enabling Cloud Resource Manager API",
  "complete": false,
  "progress": 57,
  "step": 4,
  "total_steps": 7
}
```

**Complete Response:**

```json theme={null}
{
  "state": "SUCCESS",
  "status": "Setup completed",
  "complete": true,
  "result": {
    "projects_configured": 3,
    "apis_enabled": 15
  }
}
```

### OAuth Scopes

GCP OAuth requires:

* `https://www.googleapis.com/auth/cloud-platform` - Full cloud platform access
* `https://www.googleapis.com/auth/compute` - Compute Engine
* `https://www.googleapis.com/auth/cloudresourcemanager` - Project management

### Token Management

* **Access Token** - Short-lived (1 hour), used for API calls
* **Refresh Token** - Long-lived, used to get new access tokens
* **Expiration** - Stored in database (`expires_at` field)
* **Refresh** - Automatic when access token expires

***

## Tailscale OAuth

Tailscale uses OAuth 2.0 client credentials flow.

### Flow Diagram

```mermaid theme={null}
sequenceDiagram
    participant User
    participant Frontend
    participant Aurora
    participant Tailscale
    
    User->>Frontend: Enter client credentials
    Frontend->>Aurora: POST /tailscale/connect {clientId, clientSecret}
    Aurora->>Tailscale: Request access token
    Tailscale->>Aurora: {access_token}
    Aurora->>Tailscale: Validate token (list devices)
    Tailscale->>Aurora: {devices: [...]}
    Aurora->>Aurora: Store credentials in Vault
    Aurora->>Frontend: {success: true}
```

### Client Credentials Flow

**POST /tailscale\_api/tailscale/connect**

```json theme={null}
{
  "clientId": "oauth-client-123",
  "clientSecret": "tskey-client-secret",
  "tailnet": "example.com"
}
```

Aurora:

1. Requests access token from Tailscale
2. Validates token by fetching devices
3. Stores credentials in Vault
4. Generates SSH key pair
5. Creates reusable auth key

**Response:**

```json theme={null}
{
  "success": true,
  "message": "Tailscale connected successfully",
  "tailnet": "example.com",
  "tailnetName": "Example Org",
  "deviceCount": 12
}
```

### Token Refresh

**POST /tailscale\_api/tailscale/refresh-token**

Manually refresh access token:

```json theme={null}
{
  "success": true,
  "message": "Token refreshed successfully",
  "expiresAt": "2024-03-15T12:00:00Z"
}
```

### OAuth Scopes

Tailscale scopes:

* `devices` - Read device information
* `devices:write` - Manage devices
* `acl` - Read ACL configuration
* `acl:write` - Modify ACL
* `keys` - Manage auth keys

***

## Azure Service Principal

Azure uses service principal authentication (similar to OAuth client credentials).

### Flow Diagram

```mermaid theme={null}
sequenceDiagram
    participant User
    participant Frontend
    participant Aurora
    participant Azure
    
    User->>Frontend: Enter service principal
    Frontend->>Aurora: POST /azure/login {tenantId, clientId, clientSecret}
    Aurora->>Azure: Request access token
    Azure->>Aurora: {access_token}
    Aurora->>Azure: Fetch subscriptions
    Azure->>Aurora: {subscriptions: [...]}
    Aurora->>Aurora: Store credentials in Vault
    Aurora->>Frontend: {success, subscription_id}
```

### Service Principal Authentication

**POST /azure/login**

```json theme={null}
{
  "userId": "user_123",
  "tenantId": "tenant-guid",
  "clientId": "app-guid",
  "clientSecret": "secret-value",
  "subscriptionId": "sub-guid"
}
```

Aurora:

1. Creates ClientSecretCredential
2. Requests management token
3. Validates by fetching subscriptions
4. Stores credentials in Vault

**Response:**

```json theme={null}
{
  "message": "Successfully logged in to Azure",
  "subscription_id": "sub-guid",
  "subscription_name": "Production Subscription"
}
```

### Token Expiration

* Access tokens expire after 1 hour
* Automatically refreshed on API calls
* Uses stored service principal credentials
* No manual refresh required

***

## OAuth Security

### State Parameter

Prevents CSRF attacks:

```javascript theme={null}
// Encode user_id in state
const state = encodeURIComponent(userId);
const authUrl = `${OAUTH_URL}?state=${state}`;

// Validate on callback
const userId = decodeURIComponent(request.query.state);
if (!userId) {
  throw new Error('Invalid state parameter');
}
```

### External ID (AWS)

Adds security layer for role assumption:

```json theme={null}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::AURORA_ACCOUNT:root"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "UNIQUE_EXTERNAL_ID"
        }
      }
    }
  ]
}
```

* Each workspace has unique external ID
* Prevents confused deputy problem
* Required for role assumption

### Token Storage

**HashiCorp Vault:**

```python theme={null}
# Store tokens
vault_client.write(
    f'aurora/users/{user_id}/gcp',
    {
        'access_token': access_token,
        'refresh_token': refresh_token,
        'expires_at': expires_at
    }
)

# Database stores reference
db.execute(
    "INSERT INTO user_tokens (user_id, provider, secret_ref) VALUES (%s, %s, %s)",
    (user_id, 'gcp', f'vault:kv/data/aurora/users/{user_id}/gcp')
)
```

### Credential Rotation

**GCP:**

* Access tokens expire after 1 hour
* Refresh tokens used to get new access tokens
* Refresh tokens rotated on use (optional)

**AWS:**

* STS credentials expire after 1 hour (default)
* Automatically re-assumed on expiration
* External ID never changes

**Azure:**

* Access tokens expire after 1 hour
* Client secrets expire after configured period
* No automatic rotation (manual renewal required)

**Tailscale:**

* Access tokens expire after 90 days (default)
* Manual refresh via `/tailscale/refresh-token`
* Client credentials never expire

***

## Error Handling

### OAuth Errors

**Invalid Grant:**

```json theme={null}
{
  "error": "invalid_grant",
  "error_description": "Authorization code expired"
}
```

**Action:** Restart OAuth flow

**Access Denied:**

```json theme={null}
{
  "error": "access_denied",
  "error_description": "User denied consent"
}
```

**Action:** Inform user, allow retry

**Invalid Client:**

```json theme={null}
{
  "error": "invalid_client",
  "error_description": "Invalid client credentials"
}
```

**Action:** Verify Aurora OAuth configuration

### Token Refresh Errors

**Refresh Token Expired:**

```json theme={null}
{
  "error": "invalid_grant",
  "error_description": "Token has been expired or revoked"
}
```

**Action:** Trigger re-authentication

**Network Error:**

```python theme={null}
try:
    response = oauth_client.refresh_token(refresh_token)
except requests.exceptions.RequestException as e:
    logger.error(f'Token refresh failed: {e}')
    # Retry with exponential backoff
```

***

## Best Practices

### Frontend Implementation

```javascript theme={null}
class OAuthConnector {
  async connect(provider, userId) {
    // 1. Initiate flow
    const { login_url } = await this.getLoginUrl(provider, userId);
    
    // 2. Open OAuth window
    const authWindow = window.open(login_url, '_blank', 'width=600,height=700');
    
    // 3. Poll for completion
    const result = await this.pollForCompletion(authWindow);
    
    return result;
  }
  
  async pollForCompletion(authWindow) {
    return new Promise((resolve, reject) => {
      const checkInterval = setInterval(() => {
        try {
          // Check if window closed
          if (authWindow.closed) {
            clearInterval(checkInterval);
            // Check connection status
            this.checkStatus().then(resolve);
          }
        } catch (e) {
          // Cross-origin error means still on OAuth page
        }
      }, 500);
      
      // Timeout after 5 minutes
      setTimeout(() => {
        clearInterval(checkInterval);
        authWindow.close();
        reject(new Error('OAuth timeout'));
      }, 300000);
    });
  }
}
```

### Secure Callback Handling

```python theme={null}
@app.route('/oauth/callback')
def oauth_callback():
    # 1. Validate state
    state = request.args.get('state')
    if not state:
        return 'Missing state parameter', 400
    
    user_id = decode_state(state)
    if not user_id:
        return 'Invalid state', 400
    
    # 2. Exchange code for token
    code = request.args.get('code')
    if not code:
        return 'Missing authorization code', 400
    
    try:
        tokens = exchange_code_for_tokens(code)
    except OAuthError as e:
        logger.error(f'Token exchange failed: {e}')
        return redirect(f'{FRONTEND_URL}?login=failed')
    
    # 3. Store tokens securely
    store_tokens_in_vault(user_id, tokens)
    
    # 4. Trigger post-auth setup
    task = setup_account.delay(user_id)
    
    # 5. Redirect to frontend
    return redirect(f'{FRONTEND_URL}?login=success&task_id={task.id}')
```

### Token Refresh

```python theme={null}
def get_access_token(user_id, provider):
    """Get valid access token, refreshing if needed."""
    tokens = get_tokens_from_vault(user_id, provider)
    
    # Check expiration
    if time.time() >= tokens['expires_at'] - 300:  # 5 min buffer
        # Refresh token
        new_tokens = refresh_access_token(tokens['refresh_token'])
        store_tokens_in_vault(user_id, new_tokens)
        return new_tokens['access_token']
    
    return tokens['access_token']
```
