操作指南:授权(应用程序)

前言

在本指南中,我们将学习如何在 Frame.io 项目中对 C2C 应用程序 进行身份验证和授权。

我需要准备什么?

如果您还未阅读实施 C2C:设置指南,请先快速浏览一下再继续操作!此外,您应该已收到我们团队提供的 client_id,它将用于标识您的集成。如果您尚未收到 client_id,请查阅这份 C2C 生态系统简介,并联系我们的团队。如果您收到的是 client_secret 而非 client_id,则表示我们已将您设置为硬件设备,而非 C2C 应用程序;这种情况下,您可以选择遵循硬件设备身份验证指南,或者联系我们的团队以获取 client_id

了解应用程序授权流程

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

OAuth 概述

C2C 应用程序使用 OAuth 2.0 流程进行身份验证和授权。这是一组标准化的调用,可用于对第三方应用程序或用户进行服务的身份验证和授权。您可以在此处了解更多关于 OAuth 流程的信息

回调/重定向 URI

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

  • 为您所有
  • 静态

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

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

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

设备识别

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

对于 C2C 应用程序,我们称之为设备的 device_id。在设置实施时,您应该考虑如何生成这个标识符。一些平台为此特定用例提供了 API,用于生成设备+应用程序特定的标识符:

平台参考
iOSidentifierForVendor
AndroidFID 或 GUID
请注意:不要泄露个人身份信息

例如,用户的电子邮件地址就不能作为 device_id 的有效值。同样,请确保您拥有唯一标识符。例如,请勿使用设备的 MAC 地址。MAC 地址并非由您的软件所有,也可能被视为个人身份信息。

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

步骤 1:验证用户身份

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

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

Python
1def redirect_to_auth(config):
2 credentials = {
3 "response_type": "code",
4 "redirect_uri": "http://MyApp.io/frameio-callback",
5 "client_id": f"{MYAPP.client_id}",
6 "scope": "offline device.connect asset.create",
7 "state": str(uuid.uuid4()),
8 "device_id": f"{HARDWARE.get_vendor_id('com.mycompany.myapp')}",
9 }
10
11 encoded = parse.urlencode(credentials)
12 url = "https://applications.frame.io/oauth2/auth?" + encoded
13
14 webbrowser.open(url)

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

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 是否符合预期。

state 必须是随机的

如果 state 参数不是随机的,您将会面临 CRSF 攻击的风险,即恶意行为者会伪造您的 state 参数并向您的回调地址发送恶意请求。您可以在 Auth0 的这篇博文中了解更多关于 state 参数的信息

device_id:此特定设备/安装的唯一标识符。设备 ID 应该是 为您所有 的值(因此不能是 MAC 地址/CPU 序列号等),并且 不应包含个人身份信息(因此不能是电子邮件地址、社会保险代码、指纹哈希等)。有关更多信息,请参阅上文关于 device_id 的部分

步骤 2:接收 OAuth 响应

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

完整的 URI 将类似于:

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

$$ python -m http.server 8888

现在,我们可以使用以下模板来请求 Frame.io 访问权限。填写您的 [client_id][state] 值。您可以在此处为 state 生成一个随机的 UUID

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 无法识别所请求的资源,也不知道如何响应它。但不用担心,授权请求仍然成功!我们应该会在终端中看到服务器打印出类似如下的内容:

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
1@handler("/frameio-callback")
2def do_get(request):
3 params = url.parse_query(request.url.parts.query)
4 if "error" in params:
5 raise AuthError(params["error"])
6
7 # Handles sending the authentication code and state to the proper user
8 MyApp.frameio_oauth_success(state=params["state"], code=params["code"])
9
10 # Render some sort of confirmation page for the user.
11 request.send_response(
12 code=200,
13 headers={"Content-type": "text/html"},
14 data=OauthSuccessPage()
15 )

步骤 3:获取访问令牌

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

让我们发起如下请求:

$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
OAuth 端点

请注意,此请求的主机是 applications.frame.io,而不是我们大多数请求中使用的 api.frame.io。另请注意,我们在这里使用的是表单数据而非 JSON 数据。C2C OAuth 端点仅接受表单数据。

一旦您通过身份验证,其他端点将接受 application/json 负载,但如果向身份验证端点发送 JSON 而非 application/x-www-form-urlencoded 负载,则会返回错误。

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

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

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

1{
2 "access_token": "[access_token]",
3 "expires_in": 3599,
4 "refresh_token": "[refresh_token]",
5 "scope": "offline device.connect asset.create",
6 "token_type": "bearer"
7}

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

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

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

步骤 4:列出帐户

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

$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
API 端点规范

/v2/devices/accounts 的文档可在此处找到

Authorization 标头

对于每个需要授权的端点,我们都需要将 access_token 添加到 Authorization 标头中。请注意,我们需要在访问令牌前加上 Bearer (注意后面有一个空格!)作为其值。

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

1[
2 {
3 "_type": "account",
4 "display_name": "Hogwarts General",
5 "id": "46b7ea11-3041-4e2b-97f7-98fbf5c974c9"
6 },
7 {
8 "_type": "account",
9 "display_name": "Gryffindor",
10 "id": "e6007a3d-cad7-4666-9ee3-23c1af032060"
11 },
12 {
13 "_type": "account",
14 "display_name": "QUIDDITCH LEGENDS -- LETS GOOOOOOOOOO",
15 "id": "cc94119d-f957-4d6e-b8cf-0c095211b1b9"
16 }
17]

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

步骤 5:列出项目

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

$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
API 端点规范

/v2/devices/accounts/[account_id]/projects 的文档可在此处找到

我们需要将想要列出项目所属的 account_id 添加到 URL 中。另请注意,整个资源路径以 /devices/... 开头。这里不仅仅是列出项目,而是列出 用户拥有 C2C 设备管理权限的 项目。如果未列出用户所属的某个项目,则意味着该用户没有该项目的 C2C 设备管理权限。

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

1[
2 {
3 "_type": "project",
4 "id": "921480ec-1225-424a-9447-19c61a3a1ef2",
5 "name": "Match Recordings"
6 },
7 {
8 "_type": "project",
9 "id": "ed5dbf4a-f146-416b-add0-74de98201876",
10 "name": "Year Book Material"
11 }
12]

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

步骤 6:连接到项目

现在用户已经选择了想要连接的项目,我们便可以开始操作了!完成软件设备与 Frame.io 项目配对的最后一步:

$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
API 端点规范

/v2/devices/connect 的文档可在此处找到

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

我们会收到类似如下的响应(为简洁起见,省略了部分数据):

1{
2 "_type": "project_device",
3 "asset_type": "video",
4 "authorization": {
5 "_type": "project_device_authorization",
6 "creator": {
7 "_type": "user",
8 "account_id": "93f872fb-9924-4e31-a430-2574e0742260",
9 "deleted_at": null,
10 "email": "hpotter@hoggyhoggyhogwarts.edu",
11 "id": "d473b5e1-08d2-4842-82ac-a6b01233dc2c",
12 ...
13 "name": "Harry Potter",
14 ...
15 },
16 "creator_id": "d473b5e1-08d2-4842-82ac-a6b01233dc2c",
17 "expires_at": null,
18 "id": "ee9f5949-b7fa-4c71-8480-6d4c60877c51",
19 "inserted_at": "2022-03-09T18:14:21.893283Z",
20 "project_device_id": "6a55d7f6-dfb7-46a1-bff8-a3acb2d3d1aa",
21 "scopes": {
22 ...
23 "asset_create": true,
24 ...
25 "id": "1e174fe9-5b53-48db-b556-c310c0848898",
26 "offline": true,
27 ...
28 }
29 },
30 "channels": [
31 {
32 "_type": "project_device_channel",
33 "asset_type": "video",
34 ...
35 }
36 ],
37 "creator_id": "d473b5e1-08d2-4842-82ac-a6b01233dc2c",
38 "deleted_at": null,
39 "device_id": "a8a4f3bf-196c-4748-832b-28f1d0801515",
40 "id": "93af90e7-ee89-4b47-86e6-c2750f3790b6",
41 ...
42 "name": "MyApp-62f88d2a-1ae1-45e7-a6a0-81954e0cf2ff",
43 "project": {
44 "_type": "project",
45 "id": "921480ec-1225-424a-9447-19c61a3a1ef2",
46 "name": "Testbed"
47 },
48 "project_id": "921480ec-1225-424a-9447-19c61a3a1ef2",
49 "status": "online",
50 ...
51}
一次仅支持一个项目

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

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

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

使用第三方 OAuth 库

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

以下是几个热门的 OAuth 库:

编程语言名称URL
SwiftOAuthSwiftGithub
Pythonrequests-oauthlibGithub
Flutteroauth2_clientGithub
请记住,Frame.io 会添加一个 device_id 字段来标识特定设备。这是一个额外的自定义字段。您选择使用的库很可能支持自定义字段,但请确保不要忘记添加该字段!

故障排除

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

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

**未返回我要连接的帐户或项目:**如果您在列出帐户和/或项目,但您要连接的那个没有显示出来,则可能是以下几种情况。在 Frame.io 中,导航到您想要连接的项目,然后点击 C2C Connections 选项卡。这将帮助您找出问题所在。

  • **您的帐户未启用 C2C:**如果屏幕空白,并且有一条消息提示您的帐户无法使用 C2C,那么客户经理需要在帐户设置中为您的项目启用 C2C!
  • 您不是设备管理员:如果屏幕空白,并且有一条消息提示您没有权限,那么客户经理需要更改允许连接 C2C 设备的权限,或将您添加到一个拥有这些权限的角色中。

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

下一步

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