> This page is for 平台, version 旧版.
> For other versions, use one of these documentation indexes:
> - V4 (default): https://next.developer.frame.io/platform/v4/llms.txt
> - V4 实验版: https://next.developer.frame.io/platform/v4-experimental/llms.txt
> - 旧版: https://next.developer.frame.io/platform/v2/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://next.developer.frame.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://next.developer.frame.io/_mcp/server.

# 操作指南：授权（应用程序）

## 前言

在本指南中，我们将学习如何在 [Frame.io](http://frame.io/) 项目中对 *C2C 应用程序* 进行身份验证和授权。

## 我需要准备什么？

如果您还未阅读[实施 C2C：设置](/camera-to-cloud/implementing-c2c-setting-up)指南，请先快速浏览一下再继续操作！此外，您应该已收到我们团队提供的 `client_id`，它将用于标识您的集成。如果您尚未收到 `client_id`，请查阅这份 [C2C 生态系统简介](/camera-to-cloud/getting-started-with-cloud-device-integrations)，并联系我们的团队。如果您收到的是 `client_secret` 而非 `client_id`，则表示我们已将您设置为硬件设备，而非 C2C 应用程序；这种情况下，您可以选择遵循[硬件设备身份验证指南](/platform/v2/implementing-c2c-authentication-and-authorization-hardware)，或者联系我们的团队以获取 `client_id`。

## 了解应用程序授权流程

我们首先需要从较高层次上理解所要实施的授权流程中，预期的用户体验是怎样的。请查阅以下资源，从用户的角度了解这一流程，尝试下载 [Zoelog](https://zoelog.io/) 并登录 [Frame.io](http://frame.io/)，以亲身体验 C2C 应用程序的授权过程！

## OAuth 概述

C2C 应用程序使用 OAuth 2.0 流程进行身份验证和授权。这是一组标准化的调用，可用于对第三方应用程序或用户进行服务的身份验证和授权。您可以[在此处了解更多关于 OAuth 流程的信息](https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2)。

## 回调/重定向 URI

作为 OAuth 流程的一部分，我们的服务器需要向您所控制的 URI/URL 发起 HTTP 调用。用户在浏览器中登录 [Frame.io](http://frame.io/) 后，我们会将浏览器重定向到此 URI，以便向您的应用程序提供一些信息。您的重定向 URI 必须满足以下条件：
* 为您所有
* 静态




只要满足这两个条件，便可以为您的设备注册多个有效的重定向 URI。





在 OAuth 流程中，我们将检查您的应用程序请求的回调 URI 是否属于我们存档的 URI 之一。如果不是，授权流程将失败。如果我们不进行此检查，恶意行为者可能会提供一个指向他们所控制地址的重定向 URI。

出于开发目的，我们支持使用 `http://localhost` 上的非 HTTPS 回调。

## 设备识别





在连接到 Camera to Cloud 时，每个单独的应用程序安装都需要唯一标识自身，以便我们可以在用户项目中列出设备连接。

对于 C2C 应用程序，我们称之为设备的 `device_id`。在设置实施时，您应该考虑如何生成这个标识符。一些平台为此特定用例提供了 API，用于生成设备+应用程序特定的标识符：
| 平台 | 参考 |
|---	|---	|
| iOS | [identifierForVendor](https://developer.apple.com/documentation/uikit/uidevice/1620059-identifierforvendor) |
| Android | [FID 或 GUID](https://developer.android.com/training/articles/user-data-ids) |



<Warning title="请注意：不要泄露个人身份信息">
  例如，用户的电子邮件地址就不能作为 `device_id` 的有效值。同样，*请确保您拥有唯一标识符*。例如，请勿使用设备的 MAC 地址。MAC 地址并非由您的软件所有，也可能被视为个人身份信息。
</Warning>


如果您不确定要使用什么值，我们可以一起讨论这个选择，确保选出一个合适的值，以便尽可能简化集成过程。





## 步骤 1：验证用户身份

当我决定要在 *YourApp™* 中连接到 [Frame.io](http://frame.io/) 时，我会前往 [Frame.io](http://frame.io/) 配置部分并选择“连接到项目”（或类似选项）。点击按钮后，我会被重定向到 [Frame.io](http://frame.io/) 进行登录并授权您的应用程序。

我们通过构建一个 URL 并在 Web 浏览器中打开它来实现这一点。让我们看一些类似 Python 的伪代码：





**`Python`**

```python title="Python"
def redirect_to_auth(config):
    credentials = {
        "response_type": "code",
        "redirect_uri": "http://MyApp.io/frameio-callback",
        "client_id": f"{MYAPP.client_id}",
        "scope": "offline device.connect asset.create",
        "state": str(uuid.uuid4()),
        "device_id": f"{HARDWARE.get_vendor_id('com.mycompany.myapp')}",
    }

    encoded = parse.urlencode(credentials)
    url = "https://applications.frame.io/oauth2/auth?" + encoded

    webbrowser.open(url)
```





我们的“负载”被编码在 URL 本身之中，当完全编码后，URL 将如下所示：





```text
https://applications.frame.io/oauth2/auth?response_type=code&redirect_uri=http%3A%2F%2FMyApp.io%2Fframeio-callback&client_id=[client_id]&scope=offline+device.connect+asset.create&state=[state]&device_id=62f88d2a-1ae1-45e7-a6a0-81954e0cf2ff
```





让我们稍微拆解一下这些选项：

`response_type`：OAuth 流程应该返回的内容。此值应始终为“code”。这告诉我们的 OAuth 服务器向重定向 URI 返回一个代码，随后用该代码去获取实际的授权令牌。`redirect_uri`：OAuth 服务器在响应授权请求时应发送 GET 请求的 URL/URI。`client_id`：标识您的应用程序。对于应用程序集成，此值将由 Frame.io 提供。`scope`：您的应用程序正在请求的权限列表，以空格分隔。C2C 应用程序可使用以下权限
* `offline`：应用程序可以在初始令牌过期时刷新其自身的授权。
* `device.connect`：设备可以获取用户可用于 C2C 连接的帐户和项目列表。
* `asset.create`：应用程序可以将资产上传到其所连接的项目中。




虽然可以只请求并获准这些权限的次集，但您始终希望请求全部三个权限。

`state`：与此请求关联的一个随机值。我们使用 `state` 来验证对我们重定向 URI 的调用是否来自有效请求。当您在注册的 URI 上收到回调时，应该验证 `state` 是否符合预期。
<Warning title="`state` 必须是随机的">
  如果 state 参数不是随机的，您将会面临 CRSF 攻击的风险，即恶意行为者会伪造您的 state 参数并向您的回调地址发送恶意请求。您可以在 [Auth0 的这篇博文](https://auth0.com/docs/secure/attack-protection/state-parameters)中了解更多关于 `state` 参数的信息
</Warning>
 `device_id`：此特定设备/安装的唯一标识符。设备 ID 应该是 *为您所有* 的值（因此不能是 MAC 地址/CPU 序列号等），并且 *不应包含个人身份信息*（因此不能是电子邮件地址、社会保险代码、指纹哈希等）。有关更多信息，请参阅[上文关于 device_id 的部分](/platform/v2/implementing-c2c-authentication-and-authorization-c2c-application#device-identification-device_id)。

## 步骤 2：接收 OAuth 响应

在用户登录 [Frame.io](http://frame.io/) 并在其浏览器中接受所请求的权限范围后，系统将向您的回调 URI 发出 GET 请求。该请求包含一个经过 URL 编码的负载，内含以下查询参数：`code`：将用于从 Frame.io 后端获取实际授权令牌的代码。`state`：步骤 1 中原始身份验证请求所包含的 state 值。`scope`：已被授予的权限范围/权限列表。

完整的 URI 将类似于：





```text
https://MyApp.io/frameio-callback?code=[authorization_code]&scope=offline+device.connect+asset.create&state=[state]
```





解析 URI 可能比较棘手，而您的 HTTP / 服务器库可能具有很好的资源来处理这个问题，因此在决定尝试自行解析该值之前，请先查阅相关资源！

为了进行测试，我们可以使用 Python 快速搭建一个服务器来观察 GET 请求。您的回调 URI 需要配置为 `http://localhost:8888/callback`

```shell
$ python -m http.server 8888
```

现在，我们可以使用以下模板来请求 [Frame.io](http://frame.io/) 访问权限。填写您的 `[client_id]` 和 `[state]` 值。您可以[在此处为 `state` 生成一个随机的 UUID](https://www.uuidgenerator.net/)。

```text
https://applications.frame.io/oauth2/auth?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A8888%2Fcallback&client_id=[client_id]&scope=offline+device.connect+asset.create&state=[state]&device_id=62f88d2a-1ae1-45e7-a6a0-81954e0cf2ff
```





当我们执行授权流程时，会遇到一个 404 错误。这是因为 Python 无法识别所请求的资源，也不知道如何响应它。但不用担心，授权请求仍然成功！我们应该会在终端中看到服务器打印出类似如下的内容：





```text
Serving HTTP on :: port 8888 (http://[::]:8888/) ...
::1 - - [08/Mar/2022 14:04:34] code 404, message File not found
::1 - - [08/Mar/2022 14:04:34] "GET /callback?code=[authentication_code]&scope=offline+device.connect+asset.create&state=[state] HTTP/1.1" 404 -
```

我们应该验证 state 是否与我们发送的 state 相同，而 `authentication_code` 将在下一步获取访问令牌时发挥重要作用。

在实际应用程序中，回调处理程序可能类似于：





**`Python`**

```python title="Python"
@handler("/frameio-callback")
def do_get(request):
    params = url.parse_query(request.url.parts.query)
    if "error" in params:
       raise AuthError(params["error"])

    # Handles sending the authentication code and state to the proper user
    MyApp.frameio_oauth_success(state=params["state"], code=params["code"])

    # Render some sort of confirmation page for the user.
    request.send_response(
        code=200, 
        headers={"Content-type": "text/html"}, 
        data=OauthSuccessPage()
    )

```





## 步骤 3：获取访问令牌

现在我们有了 `authorization_code`，便可以获取访问令牌了！此时，我们的访问令牌已被授予，只需向后端请求获取它即可。

让我们发起如下请求：





```shell
curl -X POST https://applications.frame.io/oauth2/token \
    --form 'client_id=[client_id]' \
    --form 'state=[state]' \
    --form 'code=[authorization_code]' \
    --form 'redirect_uri=http://localhost:8888/callback' \
    --form 'grant_type=authorization_code' \
    --form 'scope=offline device.connect asset.create' \
    | python -m json.tool
```




<Info title="OAuth 端点">
  请注意，此请求的主机是 `applications.frame.io`，而不是我们大多数请求中使用的 `api.frame.io`。另请注意，我们在这里使用的是表单数据而非 JSON 数据。*C2C OAuth 端点仅接受表单数据。*
</Info>
 一旦您通过身份验证，其他端点将接受 `application/json` 负载，但如果向身份验证端点发送 JSON 而非 `application/x-www-form-urlencoded` 负载，则会返回错误。

我们来逐一说明这些参数：

`client_id`：[Frame.io](http://frame.io/) 颁发给我们的 OAuth 应用程序标识符 `state`：我们在原始授权请求中发送给浏览器并在回调中收到的 state 值。`code`：我们在回调中收到的授权码 `redirect_uri`：我们在 Frame.io 后端注册的同一个重定向 URI。如果该值不在 [Frame.io](http://frame.io/) 为您的集成所记录的以逗号分隔的 URL 列表中，此请求将失败。`grant type`：对于软件设备授权流程，此值始终为 `authorization_code`。`scope`：必须与回调中返回的已批准权限范围相匹配。

我们应该会收到一个类似如下格式的响应：





```json
{
    "access_token": "[access_token]",
    "expires_in": 3599,
    "refresh_token": "[refresh_token]",
    "scope": "offline device.connect asset.create",
    "token_type": "bearer"
}
```





现在，我们的设备已成功获得 Frame.io 的授权！请妥善保管这些值，因为我们在后续的请求中都将需要用到它们。让我们来看看负载中包含的内容：

`access_token`：这是您访问 [Frame.io](http://frame.io/) 其余后端服务的密钥。在本教程系列后续将要发起的请求中，我们需要将其添加到请求的标头中。`expires_in`：`access_token` 的有效期，单位为秒。令牌有效期结束后，需要对其进行刷新，我们将在后续教程中详细介绍。`refresh_token`：我们可用来管理 `access_token` 的令牌。最常用于刷新授权，但也可用于撤销授权。`token_type`：对于 C2C API，将始终为 `bearer`，且不可操作。

我们距离真正连接到项目还有几个步骤，既然已经掌握了这些，让我们继续吧！





## 步骤 4：列出帐户





接下来，我们需要获取用户有资格连接到的帐户列表。这是第一个需要用到我们访问令牌的调用，我们将把它添加到标头中：





```shell
curl -X GET https://api.frame.io/v2/devices/accounts \
    --header "x-client-version: 2.0.0" \
    --header "Authorization: Bearer [access_token]" \
    | python -m json.tool
```




<Info title="API 端点规范">
  `/v2/devices/accounts` 的文档可在[此处找到](/camera-to-cloud/api-reference/applications-auth/device-list-accounts)
</Info>

<Info title="Authorization 标头">
  对于每个需要授权的端点，我们都需要将 `access_token` 添加到 `Authorization` 标头中。请注意，我们需要在访问令牌前加上 `Bearer `（注意后面有一个空格！）作为其值。
</Info>


此调用应返回用户可连接的帐户列表：





```json
[
    {
        "_type": "account",
        "display_name": "Hogwarts General",
        "id": "46b7ea11-3041-4e2b-97f7-98fbf5c974c9"
    },
    {
        "_type": "account",
        "display_name": "Gryffindor",
        "id": "e6007a3d-cad7-4666-9ee3-23c1af032060"
    },
    {
        "_type": "account",
        "display_name": "QUIDDITCH LEGENDS -- LETS GOOOOOOOOOO",
        "id": "cc94119d-f957-4d6e-b8cf-0c095211b1b9"
    }
]
```

此时，您应该向用户展示此列表，并让他们选择想要连接的帐户。然后，在下一步中，我们将使用所选帐户的 `id` 来列出用户可以连接 C2C 设备的项目。

## 步骤 5：列出项目





现在，我们需要获取所需帐户下的项目列表：





```shell
curl -X GET https://api.frame.io/v2/devices/accounts/[account_id]/projects \
    --header "x-client-version: 2.0.0" \
    --header "Authorization: Bearer [access_token]" \
    | python -m json.tool
```




<Info title="API 端点规范">
  `/v2/devices/accounts/[account_id]/projects` 的文档可在[此处找到](/camera-to-cloud/api-reference/applications-auth/device-list-projects)
</Info>
我们需要将想要列出项目所属的 `account_id` 添加到 URL 中。另请注意，整个资源路径以 `/devices/...` 开头。这里不仅仅是列出项目，而是列出 *用户拥有 C2C 设备管理权限的* 项目。如果未列出用户所属的某个项目，则意味着该用户没有该项目的 C2C 设备管理权限。

我们将收到一个类似于帐户列表的响应：





```json
[
    {
        "_type": "project",
        "id": "921480ec-1225-424a-9447-19c61a3a1ef2",
        "name": "Match Recordings"
    },
    {
        "_type": "project",
        "id": "ed5dbf4a-f146-416b-add0-74de98201876",
        "name": "Year Book Material"
    }
]
```

与帐户列表一样，应该将此列表展示给用户，供他们选择想要连接的项目。同样，下一步我们将需要用到所选项目的 `id`。

## 步骤 6：连接到项目

现在用户已经选择了想要连接的项目，我们便可以开始操作了！完成软件设备与 [Frame.io](http://frame.io/) 项目配对的最后一步：

```shell
curl -X POST https://api.frame.io/v2/devices/connect?project_id={project_id} \
    --header "x-client-version: 2.0.0" \
    --header "Authorization: Bearer [access_token]" \
    | python -m json.tool
```




<Info title="API 端点规范">
  `/v2/devices/connect` 的文档可在[此处找到](/camera-to-cloud/api-reference/applications-auth/device-project-connect)
</Info>


项目 ID 是一个 URL 查询参数，我们仍需要传递 Authorization 标头！





我们会收到类似如下的响应（为简洁起见，省略了部分数据）：





```json
{
    "_type": "project_device",
    "asset_type": "video",
    "authorization": {
        "_type": "project_device_authorization",
        "creator": {
            "_type": "user",
            "account_id": "93f872fb-9924-4e31-a430-2574e0742260",
            "deleted_at": null,
            "email": "hpotter@hoggyhoggyhogwarts.edu",
            "id": "d473b5e1-08d2-4842-82ac-a6b01233dc2c",
            ...
            "name": "Harry Potter",
            ...
        },
        "creator_id": "d473b5e1-08d2-4842-82ac-a6b01233dc2c",
        "expires_at": null,
        "id": "ee9f5949-b7fa-4c71-8480-6d4c60877c51",
        "inserted_at": "2022-03-09T18:14:21.893283Z",
        "project_device_id": "6a55d7f6-dfb7-46a1-bff8-a3acb2d3d1aa",
        "scopes": {
            ...
            "asset_create": true,
            ...
            "id": "1e174fe9-5b53-48db-b556-c310c0848898",
            "offline": true,
            ...
        }
    },
    "channels": [
        {
            "_type": "project_device_channel",
            "asset_type": "video",
            ...
        }
    ],
    "creator_id": "d473b5e1-08d2-4842-82ac-a6b01233dc2c",
    "deleted_at": null,
    "device_id": "a8a4f3bf-196c-4748-832b-28f1d0801515",
    "id": "93af90e7-ee89-4b47-86e6-c2750f3790b6",
    ...
    "name": "MyApp-62f88d2a-1ae1-45e7-a6a0-81954e0cf2ff",
    "project": {
        "_type": "project",
        "id": "921480ec-1225-424a-9447-19c61a3a1ef2",
        "name": "Testbed"
    },
    "project_id": "921480ec-1225-424a-9447-19c61a3a1ef2",
    "status": "online",
    ...
}
```




<Info title="一次仅支持一个项目">
  


一台设备一次只能与一个项目配对；如果执行配对操作连接到另一个不同的项目，将会断开与之前所连接项目之间的连接。



</Info>


如果您的响应负载看起来像上面那样：哇！您做到了！您已经授权了第一个 Camera to Cloud 设备。花点时间庆祝一下吧！





庆祝完毕后，您应该向用户显示项目名称，以确认他们连接到了正确的项目。





## 使用第三方 OAuth 库

[Frame.io](http://frame.io/) 使用标准的 OAuth2.0 流程。为了安全起见，我们强制要求使用 PKCE。有许多库可以帮您处理集成的这一部分。

以下是几个热门的 OAuth 库：




| 编程语言 | 名称 | URL |
|---	|---	|---	|
| Swift | `OAuthSwift` | [Github](https://github.com/OAuthSwift/OAuthSwift) |
| Python | `requests-oauthlib` | [Github](https://github.com/requests/requests-oauthlib) |
| Flutter | `oauth2_client` | [Github](https://pub.dev/packages/oauth2_client) |
请记住，[Frame.io](http://frame.io/) 会添加一个 `device_id` 字段来标识特定设备。这是一个额外的自定义字段。您选择使用的库很可能支持自定义字段，但请确保不要忘记添加该字段！

## 故障排除

如果您发现自己来到了这里，那说明出了问题！与第三方集成，哪能不出点差错呢？本节列出了一些常见问题，并将引导您完成最有可能解决这些问题的步骤。请浏览以下列表，看看是否有与您遇到的问题相符的情况。[错误指南](/camera-to-cloud/how-to-handle-errors)也是查找 API 错误的绝佳资源。

如果您在此处没有找到解决方案，我们很乐意听取您遇到的问题，以便我们可以将其添加到这里！

**未返回我要连接的帐户或项目：**如果您在列出帐户和/或项目，但您要连接的那个没有显示出来，则可能是以下几种情况。在 Frame.io 中，导航到您想要连接的项目，然后点击 `C2C Connections` 选项卡。这将帮助您找出问题所在。
* **您的帐户未启用 C2C：**如果屏幕空白，并且有一条消息提示您的帐户无法使用 C2C，那么客户经理需要在帐户设置中为您的项目启用 C2C！
* **您不是设备管理员**：如果屏幕空白，并且有一条消息提示您没有权限，那么客户经理需要更改允许连接 C2C 设备的权限，或将您添加到一个拥有这些权限的角色中。

**无效客户端错误：**当您向我们提供的设备信息与我们记录中的任何信息都不匹配时，会返回 `invalid_client`。这很可能意味着您的 `client_secret`、`client_id` 或 `redirect_uri` 与 Frame.io 在后端存档的信息不匹配。**错误请求错误：**当请求数据格式有误时，会返回 `bad_request`。请仔细检查您是否拼错了某个字段名称，或者忘记添加必填字段。

## 下一步





如果您还没有联系我们的团队，我们鼓励您这样做，然后继续阅读下一份指南。我们期待收到您的回复！