---
title: "通过 OAuth 连接"
description: "授权 MCP 客户端和 ThunderPhone CLI，无需共享 API 密钥。"
---

OAuth 允许应用在您批准的权限范围内连接到一个 ThunderPhone 组织。目录客户端默认应使用 OAuth。对于需要手动配置 Bearer 令牌的客户端，组织 API 密钥仍然可用。

## 批准连接

在您的 MCP 客户端中发起连接。登录 ThunderPhone，检查应用名称和请求的权限，选择一个组织，然后选择 **批准**。如果并非由您发起连接或您不信任该应用，请选择 **拒绝**。应用名称由其开发者提供，并非验证标识。

读取权限会公开指定的数据；请求 `calls:read` 时，其中包括录音和转录文本。写入权限可以更改或删除资源；通话、活动、电话号码购买和部署可能产生费用或影响生产环境。您的组织角色仍然适用。

对于 CLI 连接，请打开终端中显示的验证链接，比对八字符代码，选择您的组织，然后批准。仅打开链接不会授予访问权限。代码将在 15 分钟后过期。

## 断开应用连接

在控制台中打开 **组织 → API 密钥 → 已授权应用**，然后选择 **撤销**。这会撤销您为当前组织选择的授权，包括其访问令牌和刷新令牌。如果您想再次授权该应用，请从应用中重新连接。

## MCP 客户端发现

使用服务器 URL `https://api.thunderphone.com/v1/mcp`。未携带有效身份验证的请求会收到 `401`，并附带：

```http
WWW-Authenticate: Bearer resource_metadata="https://api.thunderphone.com/.well-known/oauth-protected-resource"
```

获取该文档，然后获取位于 `https://api.thunderphone.com/.well-known/oauth-authorization-server` 的授权服务器元数据。资源元数据也可通过 `/.well-known/oauth-protected-resource/v1/mcp` 获取。请使用返回的端点，而非自行构造端点。资源标识符为 `https://api.thunderphone.com/v1/mcp`。

服务器支持采用 **S256 PKCE** 的授权码流程、轮换刷新令牌、公共动态客户端注册、撤销和设备授权授予。没有客户端密钥或隐式授予。OpenID 发现也可通过 `/.well-known/openid-configuration` 获取；其中包含相同的授权服务器字段，以及 `subject_types_supported: ["public"]` 和用户信息端点。不支持 ID 令牌和客户端 ID 元数据文档（CIMD）。

### 注册公共客户端

将 JSON 发送至 `POST /v1/oauth/register`：

```json
{
  "client_name": "My MCP client",
  "redirect_uris": ["http://127.0.0.1:8765/callback"],
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"]
}
```

保存返回的 `client_id`。重定向必须使用 HTTPS，或在 `127.0.0.1` 或 `localhost` 上使用 HTTP。注册精确的回调 URI，包括端口和路径。不接受片段和嵌入式凭据。可选的 `client_uri` 和 `logo_uri` 必须使用 HTTPS；ThunderPhone 不会在注册期间获取它们。注册受到速率限制。已注册的客户端不会过期，并且在连接被撤销后仍保持注册状态。HTTPS 回调包括 `https://chatgpt.com/connector/oauth/<id>` 和 `https://chatgpt.com/connector_platform_oauth_redirect`。每个连接均可注册自己的客户端。

### 授权码

使用 `client_id`、精确注册的 `redirect_uri`、`response_type=code`、随机 `state`、`scope`、`code_challenge`、`code_challenge_method=S256` 以及 `resource=https://api.thunderphone.com/v1/mcp` 打开已发现的授权端点。将质询计算为新生成的高熵 PKCE 验证器的未填充 base64url SHA-256 摘要。在交换授权码前，检查返回的 `state` 和 `iss`。每个授权响应，包括拒绝和协议错误，都会通过 `iss` 标识签发者，并且必须与发现到的 `issuer` 完全匹配。无效客户端或回调的错误会在本地返回，而不会重定向至该回调。

使用表单编码在 `POST /v1/oauth/token` 交换（也接受 JSON）：

```text
grant_type=authorization_code
client_id=<your client id>
code=<single-use authorization code>
redirect_uri=<exact registered redirect URI>
code_verifier=<original PKCE verifier>
resource=https://api.thunderphone.com/v1/mcp
```

可选的 `resource` 参数可同时用于授权和令牌请求（包括刷新和设备交换）。省略时，默认使用发现到的 MCP 资源。存在时，它必须与该资源完全匹配；其他值会返回 `invalid_target`。访问令牌携带该受众，MCP 会拒绝缺少该受众或具有不同受众的令牌，并返回 `401` 和发现质询。

授权请求和授权码会在 10 分钟后过期。每个令牌响应均包含 `access_token`、`token_type`（`Bearer`）、`expires_in`（默认 3600 秒）、`refresh_token`、`scope`、`organization_id` 和 `organization_name`。仅通过 `Authorization: Bearer` 标头发送访问令牌。切勿将令牌放入 URL、日志、源代码控制或聊天中。

### 刷新和撤销

在令牌端点使用 `grant_type=refresh_token`、`client_id`、`refresh_token` 和 `resource` 刷新令牌。以原子方式保存新的刷新令牌，并停止使用旧令牌。刷新令牌在 30 天内未成功刷新后过期。可选的 `scope` 可缩小已授予的权限。始终包含 `offline_access`，并且始终会签发刷新令牌。未包含 `scope` 的初始请求仅授予 `offline_access`，因此客户端应请求所需的权限。

重复使用授权码和已轮换的刷新令牌会撤销整个授权。在一个客户端内串行执行刷新操作；重放已成功交换的凭据不是安全的重试策略。

如需断开连接，将 `token` 和 `client_id` 发送至 `POST /v1/oauth/revoke`。撤销任一令牌都会撤销其授权，包括由其签发的所有令牌。未知令牌会返回成功，而不会泄露其是否存在。控制台会话可通过 `GET /v1/oauth/grants` 列出自身授权，并可通过 `DELETE /v1/oauth/grants/<id>` 撤销其中一项，其中 `X-ThunderPhone-Org` 用于选择组织。

### 设备授权

设备授权仅限预注册客户端；动态注册的客户端会收到 `unauthorized_client`。预注册的公共客户端 `thunderphone-cli` 支持设备授权和刷新。将 `client_id=thunderphone-cli` 和 `scope` 发送至 `POST /v1/oauth/device/code`。向用户显示 `user_code` 和 `verification_uri`，或者打开 `verification_uri_complete`。

使用 `grant_type=urn:ietf:params:oauth:grant-type:device_code`、`client_id` 和 `device_code` 轮询令牌端点，并至少等待返回的 `interval`（5 秒）。收到 `authorization_pending` 时继续。在 `slow_down` 时，对每个后续请求使用响应中返回的新 `interval`（增加 5 秒）。在 `access_denied`、`expired_token` 或任何其他错误时停止。切勿自动批准设备代码。

预注册的 `thunderphone-mcp` 客户端在任意端口上接受 `http://127.0.0.1/callback` 和 `http://localhost/callback`。方案、主机、路径和查询必须匹配；令牌交换必须使用授权中精确的重定向 URI（包括端口）。动态注册的客户端要求精确匹配重定向 URI，包括端口。如需其他路径，请动态注册不同的回调。

### 账户身份和工作区域名检查

请求 `openid email` 以及客户端所需操作范围。使用 Bearer 标头中的访问令牌调用发现到的 `userinfo_endpoint`（`GET /v1/oauth/userinfo`）。成功响应包含：

```json
{
  "sub": "123",
  "email": "person@example.com",
  "email_verified": true,
  "name": "Example User",
  "org_id": 456
}
```

`sub` 是稳定的用户标识符；`org_id` 是在同意期间选择的组织。该端点要求同时具备两个身份范围，任一缺失时返回 `403`。如果账户没有已验证的电子邮件资料，则会返回带有 `error=access_denied` 的 `403`，而不会断言未经验证的电子邮件可信。无效、过期、已撤销或受众错误的令牌会返回 `401`。人工会话令牌和组织 API 密钥无法调用用户信息端点。不会签发 ID 令牌。

## 可用权限

| 类别 | 作用域 |
| --- | --- |
| 智能体和智能体导入 | `agents:read`, `agents:write` |
| 通话 | `calls:read`, `calls:write` |
| 电话号码、已拦截来电者和 VoIP | `numbers:read`, `numbers:write` |
| 知识库 | `knowledge:read`, `knowledge:write` |
| 营销活动 | `campaigns:read`, `campaigns:write` |
| 集成、Webhook 端点、MCP 服务器 | `integrations:read`, `integrations:write` |
| 语音 | `voices:read` |
| 测试场景、测试运行、验证 | `testing:read`, `testing:write` |
| 计费 | `billing:read` |
| 账户身份和已验证邮箱 | `openid`, `email`（userinfo 两者均必需） |
| 持久连接 | `offline_access`（始终包含） |

GET、HEAD 和 OPTIONS 使用读取范围；其他方法使用写入范围。无法通过 OAuth 修改语音和账单；`POST /v1/voices/preview` 使用 `voices:read`，因为它会预览语音而不更改其配置。MCP 工具会强制执行其底层 REST 操作的范围。即使授权用户同时属于两个组织，跨组织转移也会被拒绝。其他 API 类别，包括 API 密钥管理和人工账户设置，无法通过 OAuth 使用。REST 操作缺少权限时，会返回 `403`，并附带 `WWW-Authenticate: Bearer error="insufficient_scope", scope="..."`。无效或过期的访问令牌会返回 `401`。


### MCP 工具身份验证信号

`tools/list` 中的每个工具都包含 `securitySchemes: [{"type": "oauth2", "scopes": ["agents:read"]}]`，其中的范围对应该工具的 REST 操作。公开文档工具使用空范围列表，但仍需要经过身份验证的连接。

有效令牌缺少工具所需范围时，会收到 HTTP `200`，其中 JSON-RPC `result` 包含 `isError: true`、`content` 中的说明文本以及 `_meta["mcp/www_authenticate"]`。后者是一个数组，包含带有 `resource_metadata`、`error="insufficient_scope"`、`error_description` 和所需 `scope` 的 Bearer 质询。该工具不会执行。使用此质询请求扩展授权同意。缺少或无效的身份验证仍会返回 HTTP `401` 和 `WWW-Authenticate`；REST 范围失败仍会返回 HTTP `403`。

## 凭证保留

API 会在预发布环境和生产环境中每小时运行一次 `python manage.py oauth_cleanup`。它会移除过期的授权请求和设备代码。仅当其授权被撤销或整个令牌族失效后，才会清理过期的访问令牌、授权码和刷新令牌哈希。只要令牌族仍有可用的刷新令牌、授权码、已批准的设备代码或访问令牌，已使用的哈希就会被保留，因此清理操作不会禁用重放检测。
