> ## Documentation Index
> Fetch the complete documentation index at: https://docs.centure.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# External App Events

> Audit log events for OAuth authorization flows with external applications

External app events track OAuth 2.0 authorization flows when external applications request access to MCP proxies. These events record user decisions during login and consent screens, providing visibility into which applications users authorize and the scopes they grant.

<Info>
  All external app events include the standard [actor, targets, context, and metadata](/logs/audit-events#base-event-schema) fields. The schemas below show event-specific fields.
</Info>

## Event Flow

External app authorization follows a two-step OAuth flow:

1. **Login**: User authenticates and confirms identity for the external application
2. **Consent**: User reviews and grants (or denies) requested permissions

Each step generates a view event (when the screen loads) and either an approve or reject event (based on user action).

## Target Structure

All external app events include three targets to provide complete authorization context:

1. **external\_app**: The third-party application requesting access
2. **mcp\_proxy**: The MCP proxy being authorized
3. **project**: The project containing the proxy

This multi-target structure allows filtering audit logs by any resource in the authorization chain.

***

## external\_app.login\_view

Records when a user views the login authorization screen for an external application.

### Triggered When

User is redirected to `/external-apps/login` with a valid login challenge from the external application's OAuth flow.

### Event Schema

<ResponseField name="action" type="string" required>
  `external_app.login_view`
</ResponseField>

<ResponseField name="targets" type="array" required>
  <Expandable title="external_app target">
    <ResponseField name="type" type="string" required>
      `external_app`
    </ResponseField>

    <ResponseField name="id" type="string" required>
      External application's OAuth client ID
    </ResponseField>

    <ResponseField name="name" type="string" required>
      External application's display name
    </ResponseField>

    <ResponseField name="metadata.client_name" type="string" required>
      External application's display name
    </ResponseField>

    <ResponseField name="metadata.client_id" type="string" required>
      External application's OAuth client ID
    </ResponseField>
  </Expandable>

  <Expandable title="mcp_proxy target">
    <ResponseField name="type" type="string" required>
      `mcp_proxy`
    </ResponseField>

    <ResponseField name="id" type="string" required>
      MCP proxy's unique identifier
    </ResponseField>

    <ResponseField name="name" type="string" required>
      MCP proxy's display name
    </ResponseField>

    <ResponseField name="metadata.name" type="string" required>
      MCP proxy's display name
    </ResponseField>

    <ResponseField name="metadata.project_id" type="string" required>
      Project's unique identifier
    </ResponseField>

    <ResponseField name="metadata.organization_id" type="string" required>
      Organization's unique identifier
    </ResponseField>
  </Expandable>

  <Expandable title="project target">
    <ResponseField name="type" type="string" required>
      `project`
    </ResponseField>

    <ResponseField name="id" type="string" required>
      Project's unique identifier
    </ResponseField>

    <ResponseField name="name" type="string" required>
      Project's display name
    </ResponseField>

    <ResponseField name="metadata.name" type="string" required>
      Project's display name
    </ResponseField>

    <ResponseField name="metadata.organization_id" type="string" required>
      Organization's unique identifier
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="metadata.source" type="string" required>
  Always `/external-apps/login`
</ResponseField>

<ResponseField name="metadata.user_has_access_to_proxy" type="boolean" required>
  Whether the user has permission to access the requested MCP proxy based on project membership and proxy access scope
</ResponseField>

### Example Event

```json theme={null}
{
  "action": "external_app.login_view",
  "occurredAt": "2025-01-15T10:30:00.000Z",
  "version": 1,
  "actor": {
    "type": "user",
    "id": "user_01JGXYZ123",
    "name": "Alice Johnson",
    "metadata": {
      "first_name": "Alice",
      "last_name": "Johnson",
      "email": "alice@example.com",
      "impersonator_email": "",
      "impersonator_reason": ""
    }
  },
  "targets": [
    {
      "type": "external_app",
      "id": "oauth_client_abc123",
      "name": "MCP Desktop Client",
      "metadata": {
        "client_name": "MCP Desktop Client",
        "client_id": "oauth_client_abc123"
      }
    },
    {
      "type": "mcp_proxy",
      "id": "mcp_01JGXYZ789",
      "name": "Production API Proxy",
      "metadata": {
        "name": "Production API Proxy",
        "project_id": "proj_01JGXYZ456",
        "organization_id": "org_01JGXYZ001"
      }
    },
    {
      "type": "project",
      "id": "proj_01JGXYZ456",
      "name": "Backend Services",
      "metadata": {
        "name": "Backend Services",
        "organization_id": "org_01JGXYZ001"
      }
    }
  ],
  "context": {
    "location": "192.0.2.1",
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)..."
  },
  "metadata": {
    "source": "/external-apps/login",
    "user_has_access_to_proxy": true
  }
}
```

<Note>
  The `user_has_access_to_proxy` field indicates whether authorization can proceed. Users without access see a generic error message without revealing why access was denied.
</Note>

***

## external\_app.login\_approve

Records when a user approves login for an external application.

### Triggered When

User clicks the "Continue" button on the login authorization screen, confirming their identity for the external application.

### Event Schema

<ResponseField name="action" type="string" required>
  `external_app.login_approve`
</ResponseField>

<ResponseField name="targets" type="array" required>
  Same structure as `external_app.login_view`: external\_app, mcp\_proxy, and project targets
</ResponseField>

<ResponseField name="metadata.source" type="string" required>
  Always `/external-apps/login`
</ResponseField>

### Example Event

```json theme={null}
{
  "action": "external_app.login_approve",
  "occurredAt": "2025-01-15T10:31:00.000Z",
  "version": 1,
  "actor": {
    "type": "user",
    "id": "user_01JGXYZ123",
    "name": "Alice Johnson",
    "metadata": {
      "first_name": "Alice",
      "last_name": "Johnson",
      "email": "alice@example.com",
      "impersonator_email": "",
      "impersonator_reason": ""
    }
  },
  "targets": [
    {
      "type": "external_app",
      "id": "oauth_client_abc123",
      "name": "MCP Desktop Client",
      "metadata": {
        "client_name": "MCP Desktop Client",
        "client_id": "oauth_client_abc123"
      }
    },
    {
      "type": "mcp_proxy",
      "id": "mcp_01JGXYZ789",
      "name": "Production API Proxy",
      "metadata": {
        "name": "Production API Proxy",
        "project_id": "proj_01JGXYZ456",
        "organization_id": "org_01JGXYZ001"
      }
    },
    {
      "type": "project",
      "id": "proj_01JGXYZ456",
      "name": "Backend Services",
      "metadata": {
        "name": "Backend Services",
        "organization_id": "org_01JGXYZ001"
      }
    }
  ],
  "context": {
    "location": "192.0.2.1",
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)..."
  },
  "metadata": {
    "source": "/external-apps/login"
  }
}
```

***

## external\_app.login\_reject

Records when a user rejects login for an external application.

### Triggered When

User clicks the "Cancel" button on the login authorization screen, denying their identity confirmation for the external application.

### Event Schema

<ResponseField name="action" type="string" required>
  `external_app.login_reject`
</ResponseField>

<ResponseField name="targets" type="array" required>
  Same structure as `external_app.login_view`: external\_app, mcp\_proxy, and project targets
</ResponseField>

<ResponseField name="metadata.source" type="string" required>
  Always `/external-apps/login`
</ResponseField>

### Example Event

```json theme={null}
{
  "action": "external_app.login_reject",
  "occurredAt": "2025-01-15T10:31:30.000Z",
  "version": 1,
  "actor": {
    "type": "user",
    "id": "user_01JGXYZ123",
    "name": "Alice Johnson",
    "metadata": {
      "first_name": "Alice",
      "last_name": "Johnson",
      "email": "alice@example.com",
      "impersonator_email": "",
      "impersonator_reason": ""
    }
  },
  "targets": [
    {
      "type": "external_app",
      "id": "oauth_client_abc123",
      "name": "MCP Desktop Client",
      "metadata": {
        "client_name": "MCP Desktop Client",
        "client_id": "oauth_client_abc123"
      }
    },
    {
      "type": "mcp_proxy",
      "id": "mcp_01JGXYZ789",
      "name": "Production API Proxy",
      "metadata": {
        "name": "Production API Proxy",
        "project_id": "proj_01JGXYZ456",
        "organization_id": "org_01JGXYZ001"
      }
    },
    {
      "type": "project",
      "id": "proj_01JGXYZ456",
      "name": "Backend Services",
      "metadata": {
        "name": "Backend Services",
        "organization_id": "org_01JGXYZ001"
      }
    }
  ],
  "context": {
    "location": "192.0.2.1",
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)..."
  },
  "metadata": {
    "source": "/external-apps/login"
  }
}
```

***

## external\_app.consent\_view

Records when a user views the consent authorization screen for an external application.

### Triggered When

User is redirected to `/external-apps/consent` with a valid consent challenge after successful login, showing the requested OAuth scopes.

### Event Schema

<ResponseField name="action" type="string" required>
  `external_app.consent_view`
</ResponseField>

<ResponseField name="targets" type="array" required>
  Same structure as `external_app.login_view`: external\_app, mcp\_proxy, and project targets
</ResponseField>

<ResponseField name="metadata.source" type="string" required>
  Always `/external-apps/consent`
</ResponseField>

<ResponseField name="metadata.user_has_access_to_proxy" type="boolean" required>
  Whether the user has permission to access the requested MCP proxy
</ResponseField>

<ResponseField name="metadata.requested_scopes" type="string" required>
  Comma-separated list of OAuth scopes requested by the external application (e.g., `"openid, profile, email"`)
</ResponseField>

### Example Event

```json theme={null}
{
  "action": "external_app.consent_view",
  "occurredAt": "2025-01-15T10:31:15.000Z",
  "version": 1,
  "actor": {
    "type": "user",
    "id": "user_01JGXYZ123",
    "name": "Alice Johnson",
    "metadata": {
      "first_name": "Alice",
      "last_name": "Johnson",
      "email": "alice@example.com",
      "impersonator_email": "",
      "impersonator_reason": ""
    }
  },
  "targets": [
    {
      "type": "external_app",
      "id": "oauth_client_abc123",
      "name": "MCP Desktop Client",
      "metadata": {
        "client_name": "MCP Desktop Client",
        "client_id": "oauth_client_abc123"
      }
    },
    {
      "type": "mcp_proxy",
      "id": "mcp_01JGXYZ789",
      "name": "Production API Proxy",
      "metadata": {
        "name": "Production API Proxy",
        "project_id": "proj_01JGXYZ456",
        "organization_id": "org_01JGXYZ001"
      }
    },
    {
      "type": "project",
      "id": "proj_01JGXYZ456",
      "name": "Backend Services",
      "metadata": {
        "name": "Backend Services",
        "organization_id": "org_01JGXYZ001"
      }
    }
  ],
  "context": {
    "location": "192.0.2.1",
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)..."
  },
  "metadata": {
    "source": "/external-apps/consent",
    "user_has_access_to_proxy": true,
    "requested_scopes": "openid, profile, email"
  }
}
```

***

## external\_app.consent\_approve

Records when a user approves consent and grants permissions to an external application.

### Triggered When

User clicks the "Allow" button on the consent screen, granting the requested OAuth scopes to the external application.

### Event Schema

<ResponseField name="action" type="string" required>
  `external_app.consent_approve`
</ResponseField>

<ResponseField name="targets" type="array" required>
  Same structure as `external_app.login_view`: external\_app, mcp\_proxy, and project targets
</ResponseField>

<ResponseField name="metadata.source" type="string" required>
  Always `/external-apps/consent`
</ResponseField>

<ResponseField name="metadata.granted_scopes" type="string" required>
  Comma-separated list of OAuth scopes actually granted to the application. This is the intersection of requested scopes and scopes the user had permission to grant.
</ResponseField>

### Example Event

```json theme={null}
{
  "action": "external_app.consent_approve",
  "occurredAt": "2025-01-15T10:32:00.000Z",
  "version": 1,
  "actor": {
    "type": "user",
    "id": "user_01JGXYZ123",
    "name": "Alice Johnson",
    "metadata": {
      "first_name": "Alice",
      "last_name": "Johnson",
      "email": "alice@example.com",
      "impersonator_email": "",
      "impersonator_reason": ""
    }
  },
  "targets": [
    {
      "type": "external_app",
      "id": "oauth_client_abc123",
      "name": "MCP Desktop Client",
      "metadata": {
        "client_name": "MCP Desktop Client",
        "client_id": "oauth_client_abc123"
      }
    },
    {
      "type": "mcp_proxy",
      "id": "mcp_01JGXYZ789",
      "name": "Production API Proxy",
      "metadata": {
        "name": "Production API Proxy",
        "project_id": "proj_01JGXYZ456",
        "organization_id": "org_01JGXYZ001"
      }
    },
    {
      "type": "project",
      "id": "proj_01JGXYZ456",
      "name": "Backend Services",
      "metadata": {
        "name": "Backend Services",
        "organization_id": "org_01JGXYZ001"
      }
    }
  ],
  "context": {
    "location": "192.0.2.1",
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)..."
  },
  "metadata": {
    "source": "/external-apps/consent",
    "granted_scopes": "openid, profile, email"
  }
}
```

<Note>
  The `granted_scopes` field may differ from `requested_scopes` in the view event if the application requested scopes the user cannot grant. The system automatically filters to only grant valid scopes.
</Note>

***

## external\_app.consent\_reject

Records when a user rejects consent and denies permissions to an external application.

### Triggered When

User clicks the "Deny" button on the consent screen, refusing to grant the requested OAuth scopes to the external application.

### Event Schema

<ResponseField name="action" type="string" required>
  `external_app.consent_reject`
</ResponseField>

<ResponseField name="targets" type="array" required>
  Same structure as `external_app.login_view`: external\_app, mcp\_proxy, and project targets
</ResponseField>

<ResponseField name="metadata.source" type="string" required>
  Always `/external-apps/consent`
</ResponseField>

### Example Event

```json theme={null}
{
  "action": "external_app.consent_reject",
  "occurredAt": "2025-01-15T10:32:30.000Z",
  "version": 1,
  "actor": {
    "type": "user",
    "id": "user_01JGXYZ123",
    "name": "Alice Johnson",
    "metadata": {
      "first_name": "Alice",
      "last_name": "Johnson",
      "email": "alice@example.com",
      "impersonator_email": "",
      "impersonator_reason": ""
    }
  },
  "targets": [
    {
      "type": "external_app",
      "id": "oauth_client_abc123",
      "name": "MCP Desktop Client",
      "metadata": {
        "client_name": "MCP Desktop Client",
        "client_id": "oauth_client_abc123"
      }
    },
    {
      "type": "mcp_proxy",
      "id": "mcp_01JGXYZ789",
      "name": "Production API Proxy",
      "metadata": {
        "name": "Production API Proxy",
        "project_id": "proj_01JGXYZ456",
        "organization_id": "org_01JGXYZ001"
      }
    },
    {
      "type": "project",
      "id": "proj_01JGXYZ456",
      "name": "Backend Services",
      "metadata": {
        "name": "Backend Services",
        "organization_id": "org_01JGXYZ001"
      }
    }
  ],
  "context": {
    "location": "192.0.2.1",
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)..."
  },
  "metadata": {
    "source": "/external-apps/consent"
  }
}
```

***

## Authorization Security

### Access Validation

All authorization requests validate that the user has access to the requested MCP proxy based on:

* **Project membership**: User must be a member of the project containing the proxy
* **Proxy access scope**: Proxy's configured access restrictions (organization-wide, project-wide, or specific users)

Failed validation results in a generic error message that does not reveal the specific reason for denial. View events record `user_has_access_to_proxy: false` in these cases.

### Challenge Expiration

OAuth challenges have a limited lifetime (typically 10 minutes). Expired challenges result in validation errors without generating audit log events, as the request is invalid before authorization begins.

### External App Isolation

External applications do not belong to organizations. The `external_app` target contains only client metadata from the OAuth provider, not organization context. Authorization context comes from the linked MCP proxy and its parent project and organization.

***

## Related Events

<CardGroup cols={2}>
  <Card title="MCP Proxies" icon="plug" href="/logs/events/mcp-proxies">
    MCP proxy configuration and management events
  </Card>

  <Card title="Projects" icon="folder" href="/logs/events/projects">
    Project creation and settings changes
  </Card>

  <Card title="Project Memberships" icon="users" href="/logs/events/project-memberships">
    User access grants for projects containing proxies
  </Card>

  <Card title="API Keys" icon="key" href="/logs/events/api-keys">
    Alternative authentication method for programmatic access
  </Card>
</CardGroup>
