---
title: "Kết nối bằng OAuth"
description: "Ủy quyền cho các ứng dụng khách MCP và CLI ThunderPhone mà không cần chia sẻ khóa API."
---

OAuth cho phép một ứng dụng kết nối với một tổ chức ThunderPhone bằng các quyền bạn phê duyệt. Ứng dụng thư mục nên sử dụng OAuth theo mặc định. Khóa API tổ chức vẫn khả dụng cho các ứng dụng yêu cầu token Bearer được cấu hình thủ công.

## Phê duyệt kết nối

Bắt đầu kết nối trong ứng dụng MCP của bạn. Đăng nhập vào ThunderPhone, kiểm tra tên ứng dụng và các quyền được yêu cầu, chọn một tổ chức rồi chọn **Phê duyệt**. Chọn **Từ chối** nếu bạn không bắt đầu kết nối hoặc không tin cậy ứng dụng. Tên ứng dụng do nhà phát triển của chúng cung cấp và không phải là dấu hiệu xác minh.

Quyền đọc cho phép truy cập dữ liệu được nêu tên, bao gồm bản ghi âm và bản chép lời khi `calls:read` được yêu cầu. Quyền ghi có thể thay đổi hoặc xóa tài nguyên; cuộc gọi, chiến dịch, mua số điện thoại và triển khai có thể phát sinh chi phí hoặc ảnh hưởng đến môi trường production. Vai trò của bạn trong tổ chức vẫn được áp dụng.

Đối với kết nối CLI, hãy mở liên kết xác minh hiển thị trong terminal, so sánh mã gồm tám ký tự, chọn tổ chức của bạn rồi phê duyệt. Chỉ mở liên kết không cấp quyền truy cập. Mã hết hạn sau 15 phút.

## Ngắt kết nối ứng dụng

Mở **Tổ chức → Khóa API → Ứng dụng được ủy quyền** trong dashboard và chọn **Thu hồi**. Thao tác này thu hồi quyền ủy quyền bạn đã chọn cho tổ chức hiện tại, bao gồm token truy cập và token làm mới của tổ chức đó. Kết nối lại từ ứng dụng nếu bạn muốn ủy quyền lại.

## Khám phá dành cho ứng dụng MCP

Sử dụng URL máy chủ `https://api.thunderphone.com/v1/mcp`. Một yêu cầu không có xác thực hợp lệ sẽ nhận `401` với:

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

Tải tài liệu đó, sau đó tải siêu dữ liệu của máy chủ ủy quyền tại `https://api.thunderphone.com/.well-known/oauth-authorization-server`. Siêu dữ liệu tài nguyên cũng có tại `/.well-known/oauth-protected-resource/v1/mcp`. Sử dụng các endpoint được trả về thay vì tự tạo chúng. Mã định danh tài nguyên là `https://api.thunderphone.com/v1/mcp`.

Máy chủ hỗ trợ mã ủy quyền với **S256 PKCE**, refresh token luân phiên, đăng ký ứng dụng công khai động, thu hồi và cấp ủy quyền thiết bị. Không có client secret hoặc implicit grant. Khám phá OpenID cũng có tại `/.well-known/openid-configuration`; tài liệu này bao gồm cùng các trường máy chủ ủy quyền, cùng với `subject_types_supported: ["public"]` và endpoint userinfo. ID token và Tài liệu Siêu dữ liệu Client ID (CIMD) không được hỗ trợ.

### Đăng ký ứng dụng công khai

Gửi JSON đến `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"]
}
```

Lưu `client_id` được trả về. Chuyển hướng phải dùng HTTPS, hoặc HTTP trên `127.0.0.1` hoặc `localhost`. Đăng ký URI callback chính xác, bao gồm cổng và đường dẫn. Fragment và thông tin xác thực nhúng sẽ bị từ chối. `client_uri` và `logo_uri` không bắt buộc phải dùng HTTPS; ThunderPhone không tải chúng trong quá trình đăng ký. Đăng ký bị giới hạn tốc độ. Ứng dụng đã đăng ký không hết hạn và vẫn được đăng ký khi một kết nối bị thu hồi. Callback HTTPS bao gồm `https://chatgpt.com/connector/oauth/<id>` và `https://chatgpt.com/connector_platform_oauth_redirect`. Mỗi kết nối có thể đăng ký ứng dụng riêng.

### Mã ủy quyền

Mở endpoint ủy quyền đã khám phá với `client_id`, `redirect_uri` đã đăng ký chính xác, `response_type=code`, `state` ngẫu nhiên, `scope`, `code_challenge`, `code_challenge_method=S256` và `resource=https://api.thunderphone.com/v1/mcp`. Tính challenge dưới dạng mã băm SHA-256 base64url không đệm của một verifier PKCE mới có entropy cao. Kiểm tra `state` và `iss` được trả về trước khi đổi mã. Mọi phản hồi ủy quyền, bao gồm từ chối và lỗi giao thức, đều xác định bên phát hành bằng `iss`, khớp chính xác với `issuer` đã khám phá. Lỗi do ứng dụng hoặc callback không hợp lệ được trả về cục bộ mà không chuyển hướng đến callback đó.

Đổi tại `POST /v1/oauth/token` bằng mã hóa biểu mẫu (JSON cũng được chấp nhận):

```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
```

Tham số `resource` không bắt buộc được chấp nhận trên cả yêu cầu ủy quyền và token (bao gồm đổi refresh token và thiết bị). Khi bị bỏ qua, tham số này mặc định là tài nguyên MCP đã khám phá. Khi có mặt, tham số này phải khớp chính xác với tài nguyên đó; các giá trị khác trả về `invalid_target`. Access token mang audience đó và MCP từ chối token thiếu audience hoặc có audience khác bằng `401` và một challenge khám phá.

Yêu cầu ủy quyền và mã hết hạn sau 10 phút. Mọi phản hồi token đều chứa `access_token`, `token_type` (`Bearer`), `expires_in` (mặc định 3600 giây), `refresh_token`, `scope`, `organization_id` và `organization_name`. Chỉ gửi access token qua header `Authorization: Bearer`. Không bao giờ đặt token trong URL, log, quản lý mã nguồn hoặc chat.

### Làm mới và thu hồi

Làm mới bằng `grant_type=refresh_token`, `client_id`, `refresh_token` và `resource` tại endpoint token. Lưu refresh token mới theo cách nguyên tử và ngừng sử dụng token cũ. Refresh token hết hạn sau 30 ngày không làm mới thành công. `scope` không bắt buộc có thể thu hẹp các quyền đã cấp. `offline_access` luôn được bao gồm và refresh token luôn được cấp. Một yêu cầu ban đầu không có `scope` chỉ cấp `offline_access`, vì vậy ứng dụng nên yêu cầu các quyền cần thiết.

Việc tái sử dụng mã và refresh token đã luân phiên sẽ thu hồi toàn bộ ủy quyền. Tuần tự hóa các lần làm mới trong một ứng dụng; phát lại thông tin xác thực đã đổi thành công không phải là chiến lược thử lại an toàn.

Để ngắt kết nối, gửi `token` và `client_id` đến `POST /v1/oauth/revoke`. Thu hồi một trong hai token sẽ thu hồi ủy quyền của token đó, bao gồm mọi token được cấp từ token đó. Một token không xác định trả về thành công mà không tiết lộ token đó có tồn tại hay không. Phiên Dashboard có thể liệt kê các ủy quyền của chính mình tại `GET /v1/oauth/grants` và thu hồi một ủy quyền tại `DELETE /v1/oauth/grants/<id>`, trong đó `X-ThunderPhone-Org` chọn tổ chức.

### Ủy quyền thiết bị

Ủy quyền thiết bị bị giới hạn cho các ứng dụng đã đăng ký trước; ứng dụng đăng ký động nhận `unauthorized_client`. Ứng dụng công khai đã đăng ký trước `thunderphone-cli` hỗ trợ ủy quyền thiết bị và làm mới. Gửi `client_id=thunderphone-cli` và `scope` đến `POST /v1/oauth/device/code`. Hiển thị `user_code` và `verification_uri` cho người dùng hoặc mở `verification_uri_complete`.

Thăm dò endpoint token bằng `grant_type=urn:ietf:params:oauth:grant-type:device_code`, `client_id` và `device_code`, chờ ít nhất `interval` được trả về (5 giây). Tiếp tục khi nhận `authorization_pending`. Với `slow_down`, sử dụng `interval` mới được trả về trong phản hồi (tăng thêm 5 giây) cho mọi yêu cầu tiếp theo. Dừng khi nhận `access_denied`, `expired_token` hoặc bất kỳ lỗi nào khác. Không bao giờ tự động phê duyệt mã thiết bị.

Ứng dụng đã đăng ký trước `thunderphone-mcp` chấp nhận `http://127.0.0.1/callback` và `http://localhost/callback` trên mọi cổng. Scheme, host, đường dẫn và query phải khớp; quá trình đổi token phải sử dụng URI chuyển hướng chính xác (bao gồm cổng) từ quá trình ủy quyền. Ứng dụng đăng ký động yêu cầu URI chuyển hướng khớp chính xác, bao gồm cổng. Đăng ký động một callback khác nếu bạn cần đường dẫn khác.

### Kiểm tra danh tính tài khoản và tên miền không gian làm việc

Yêu cầu cả `openid email` cùng với các scope thao tác mà ứng dụng của bạn cần. Gọi `userinfo_endpoint` đã khám phá (`GET /v1/oauth/userinfo`) với access token trong header Bearer. Một phản hồi thành công chứa:

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

`sub` là mã định danh người dùng ổn định; `org_id` là tổ chức được chọn trong quá trình đồng ý. Endpoint yêu cầu cả hai scope danh tính và trả về `403` nếu thiếu một trong hai. Endpoint trả về `403` với `error=access_denied` nếu tài khoản không có hồ sơ email đã xác minh, thay vì khẳng định rằng email chưa xác minh là đáng tin cậy. Token không hợp lệ, hết hạn, bị thu hồi hoặc có audience không đúng sẽ trả về `401`. Token phiên người dùng và API key tổ chức không thể gọi userinfo. Không có ID token nào được cấp.

## Quyền có sẵn

| Nhóm | Phạm vi |
| --- | --- |
| Tác nhân AI và nhập tác nhân AI | `agents:read`, `agents:write` |
| Cuộc gọi | `calls:read`, `calls:write` |
| Số điện thoại, người gọi bị chặn và VoIP | `numbers:read`, `numbers:write` |
| Kiến thức | `knowledge:read`, `knowledge:write` |
| Chiến dịch | `campaigns:read`, `campaigns:write` |
| Tích hợp, điểm cuối webhook, máy chủ MCP | `integrations:read`, `integrations:write` |
| Giọng nói | `voices:read` |
| Kịch bản kiểm thử, lượt chạy kiểm thử, xác thực | `testing:read`, `testing:write` |
| Thanh toán | `billing:read` |
| Danh tính tài khoản và email đã xác minh | `openid`, `email` (cả hai đều bắt buộc cho userinfo) |
| Kết nối liên tục | `offline_access` (luôn được bao gồm) |

GET, HEAD và OPTIONS sử dụng phạm vi đọc; các phương thức khác sử dụng phạm vi ghi. Không thể thực hiện thay đổi giọng nói và thanh toán qua OAuth; `POST /v1/voices/preview` sử dụng `voices:read` vì endpoint này xem trước giọng nói mà không thay đổi cấu hình của giọng nói. Công cụ MCP áp dụng phạm vi của thao tác REST nền tảng. Việc chuyển giữa các tổ chức bị từ chối ngay cả khi người dùng cấp quyền thuộc cả hai tổ chức. Các nhóm API khác, bao gồm quản lý API key và cài đặt tài khoản người dùng, không khả dụng qua OAuth. Thiếu quyền trên một thao tác REST sẽ trả về `403` cùng với `WWW-Authenticate: Bearer error="insufficient_scope", scope="..."`. Access token không hợp lệ hoặc đã hết hạn sẽ trả về `401`.


### Tín hiệu xác thực công cụ MCP

Mọi công cụ trong `tools/list` đều bao gồm `securitySchemes: [{"type": "oauth2", "scopes": ["agents:read"]}]`, với phạm vi của thao tác REST tương ứng của công cụ đó. Công cụ tài liệu công khai sử dụng danh sách phạm vi trống và vẫn yêu cầu kết nối đã được xác thực.

Một token hợp lệ nhưng thiếu phạm vi của công cụ sẽ nhận HTTP `200` với một `result` JSON-RPC chứa `isError: true`, văn bản giải thích trong `content` và `_meta["mcp/www_authenticate"]`. Thành phần sau là một mảng chứa thử thách Bearer với `resource_metadata`, `error="insufficient_scope"`, `error_description` và `scope` bắt buộc. Công cụ không được thực thi. Sử dụng thử thách này để yêu cầu sự đồng ý mở rộng. Xác thực thiếu hoặc không hợp lệ vẫn trả về HTTP `401` cùng với `WWW-Authenticate`; lỗi phạm vi REST vẫn trả về HTTP `403`.

## Lưu giữ thông tin xác thực

API chạy `python manage.py oauth_cleanup` mỗi giờ trong staging và production. Lệnh này xóa các yêu cầu cấp quyền và mã thiết bị đã hết hạn. Access token, mã cấp quyền và hash refresh token đã hết hạn chỉ được dọn dẹp sau khi quyền cấp của chúng bị thu hồi hoặc toàn bộ nhóm token không còn hoạt động. Các hash đã sử dụng được giữ lại khi nhóm token vẫn có refresh token, mã cấp quyền, mã thiết bị đã phê duyệt hoặc access token có thể sử dụng, để quá trình dọn dẹp không thể vô hiệu hóa việc phát hiện phát lại.
