Frame.io TypeScript SDK — 身份验证指南

本指南介绍如何使用 Frame.io TypeScript SDK (frameio) 通过 Frame.io API 进行身份验证。 Frame.io V4 API 使用 Adobe Identity Management Service (IMS),Adobe 的 OAuth 2.0 身份平台。 这是面向 TypeScript/JavaScript 开发者的独立参考。 下方的所有代码示例和流程仅适用于 frameio 包。


TypeScript SDK 中的身份验证类型

TypeScript SDK 支持四种 OAuth 身份验证类,以及直接使用令牌的方式:

方法用例是否需要用户交互?是否需要客户端密钥?
静态令牌快速脚本、测试,或者您已拥有令牌
服务器到服务器后端服务、定时任务、自动化
Web 应用程序服务器端应用程序(Express、Fastify、Next.js)
SPA (PKCE)无法存储密钥的浏览器应用程序
原生应用程序 (PKCE)使用自定义 URI 方案进行重定向的桌面/移动应用程序
服务器到服务器方式让您的应用程序以服务帐户身份运行,无需用户交互。此方式仅适用于通过 Adobe Admin Console 管理的 Frame.io V4 帐户。Web 应用程序SPA 方式让您的应用程序以特定用户的身份运行。两者底层都使用 Adobe IMS:用户授权您的应用程序后,SDK 会将生成的代码交换为令牌。TypeScript SDK 为您处理 IMS 的 /authorize/v2/token/v3 流程。对于 Web 应用程序,您需要客户端密钥;对于 SPA,则改用 PKCE原生应用程序遵循与 SPA 相同的 PKCE 流程,但使用 Adobe 为您的原生应用程序凭据分配的 adobe+<hash>://callback</hash> 重定向 URI。这让您的应用程序能够在授权完成后,于操作系统层面拦截该重定向。

服务帐户用户

使用服务器到服务器的身份验证时,您的应用程序充当服务帐户用户,这是一种可以代表服务执行操作的独特帐户类型。 这些内容在 Frame.io 中对其他用户可见:当服务帐户执行操作时,其名称会显示在 UI 中。 您可以通过 Adobe Admin ConsoleDeveloper Console 授予和撤销服务帐户访问权限。 服务帐户名称通过 Frame.io UI 进行管理。 默认情况下,您的第一个 S2S 连接名为 Service Account User,第二个名为 Service Account User 2,以此类推。


快速入门

前提条件

  1. Adobe Developer Console 提供的 凭据
  • 客户端 ID — 所有 OAuth 流均需要 - 客户端密钥 — 服务器到服务器的流和 Web 应用程序流需要 - 重定向 URI — Web 应用程序、SPA 和原生应用程序流需要;必须注册在 Adobe 项目中
  1. 安装 SDK:
$npm install frameio

选择方法

  • 无用户参与? 使用服务器到服务器 (ServerToServerAuth)。
  • 有用户参与且您可以存储密钥? 使用 Web 应用程序 (WebAppAuth)。
  • 有用户参与但您无法存储密钥? 对于浏览器应用程序,使用 SPA (SPAAuth);对于桌面/移动应用程序,使用 原生应用程序 (NativeAppAuth)。

访问令牌

如果您已有访问令牌(来自其他 OAuth 系统或之前的交换,例如通过我们的 API Explorer获得),那么您可以直接传递:

1import { FrameioClient } from "frameio";
2
3const client = new FrameioClient({ token: "YOUR_ACCESS_TOKEN" });

这是最简单的方法,但令牌最终会过期,SDK 不会为您刷新令牌。

旧版开发者令牌

对于尚未通过 Adobe Admin Console 进行管理的 V4 迁移帐户,您可以继续使用来自 Frame.io 开发者网站的旧版开发者令牌。 您必须包含 x-frameio-legacy-token-auth 标头并将其设置为 true

1import { FrameioClient } from "frameio";
2
3const client = new FrameioClient({
4 token: "YOUR_LEGACY_DEVELOPER_TOKEN",
5 headers: { "x-frameio-legacy-token-auth": "true" },
6});

旧版开发者令牌不会过期,但它们是一种过渡机制。 对于新的集成和生产工作负载,我们推荐使用下面的其中一个 OAuth 2.0 流。 有关详细信息,请参阅迁移指南


服务器到服务器(客户端凭据)

将此用于需要 Frame.io 访问权限且无需用户交互的后端服务和脚本。 此流仅适用于通过 Adobe Admin Console 管理的 Frame.io V4 帐户。 您的应用程序以服务帐户用户的身份进行身份验证,无需人工干预。

1import { FrameioClient, ServerToServerAuth } from "frameio";
2
3const auth = new ServerToServerAuth({
4 clientId: "YOUR_CLIENT_ID",
5 clientSecret: "YOUR_CLIENT_SECRET",
6});
7
8const client = new FrameioClient({ token: () => auth.getToken() });

就是这样。 auth.getToken() 是 SDK 在每个请求中都会调用的异步函数。 如果当前令牌仍然有效,它就会立即返回。 如果即将过期,它会先获取一个新令牌,过程完全透明。

工作原理

您的客户端凭据(客户端 ID + 密钥)永不过期。 您只需出于安全维护目的手动轮换它们即可。 S2S 可为您提供实际上永久、不间断的 API 访问权限,无需手动干预。

底层原理:

  1. 对于第一次 API 调用,getToken() 使用 client_credentials 授权凭证从 Adobe IMS 请求新的访问令牌。
  2. 令牌会缓存在内存中。 单个访问令牌会过期(通常为 24 小时),但这会为您自动处理。
  3. 当缓存的令牌在刷新缓冲时间内时(默认:过期时间前 60 秒),SDK 会使用相同的客户端凭据自动获取新的令牌。
  4. 不涉及刷新令牌。 客户端凭据本身就是长期有效的密钥,始终可用于生成新的访问令牌。

显式身份验证

如果您想主动获取令牌(例如,在启动时因错误凭据而快速失败):

1const auth = new ServerToServerAuth({ clientId: "...", clientSecret: "..." });
2await auth.authenticate(); // throws AuthenticationError if credentials are invalid
3const client = new FrameioClient({ token: () => auth.getToken() });

Web 应用程序(授权代码)

将此用于用户使用其 Adobe ID 登录的服务器端应用程序。 此流需要客户端密钥,必须安全地将其存储在您的服务器上。

1

将用户重定向到 Adobe IMS

1 import { WebAppAuth } from "frameio";
2 import crypto from "crypto";
3
4 const auth = new WebAppAuth({
5 clientId: "YOUR_CLIENT_ID",
6 clientSecret: "YOUR_CLIENT_SECRET",
7 redirectUri: "https://yourapp.com/callback",
8 });
9
10 // Generate a cryptographically random state value to prevent CSRF attacks
11 const state = crypto.randomBytes(32).toString("hex");
12
13 const authorizationUrl = auth.getAuthorizationUrl({ state });
14 // Store `state` in the user's session, then redirect them to `authorizationUrl`
2

处理回调

当 Adobe IMS 将用户重定向回您的 redirectUri 时,提取 codestate 参数。 验证状态是否与您存储的内容匹配,然后将代码交换为令牌:

1 // In your callback handler (e.g. an Express route):
2 await auth.exchangeCode(req.query.code as string);

这会将授权代码交换为访问令牌和刷新令牌,并将两者都存储在内部。

3

使用客户端

1 import { FrameioClient } from "frameio";
2
3 const client = new FrameioClient({ token: () => auth.getToken() });

就是这样。 从现在开始,getToken() 会自动管理令牌生命周期。 当访问令牌即将过期时,SDK 会使用刷新令牌获取新令牌。 无需用户交互。

完整的表达式示例

1import crypto from "crypto";
2import express from "express";
3import session from "express-session";
4import { FrameioClient, WebAppAuth } from "frameio";
5
6const auth = new WebAppAuth({
7 clientId: "YOUR_CLIENT_ID",
8 clientSecret: "YOUR_CLIENT_SECRET",
9 redirectUri: "http://localhost:3000/callback",
10});
11
12const app = express();
13app.use(session({ secret: crypto.randomBytes(32).toString("hex"), resave: false, saveUninitialized: false }));
14
15app.get("/login", (req, res) => {
16 const state = crypto.randomBytes(32).toString("hex");
17 (req.session as any).oauthState = state;
18 res.redirect(auth.getAuthorizationUrl({ state }));
19});
20
21app.get("/callback", async (req, res) => {
22 if (req.query.state !== (req.session as any).oauthState) {
23 return res.status(403).send("Invalid state parameter");
24 }
25
26 await auth.exchangeCode(req.query.code as string);
27
28 const client = new FrameioClient({ token: () => auth.getToken() });
29 const accounts = await client.accounts.index();
30 res.json(accounts);
31});
32
33app.listen(3000);

单页面应用程序 / PKCE(授权代码 + PKCE)

将其用于无法安全存储客户端密钥的基于浏览器的应用程序、桌面应用程序或 CLI 工具。 此流使用 PKCE (RFC 7636) 来保护授权代码交换。

1

生成授权 URL

1 import { SPAAuth } from "frameio";
2
3 const auth = new SPAAuth({
4 clientId: "YOUR_CLIENT_ID",
5 redirectUri: "https://yourapp.com/callback",
6 });
7
8 const state = crypto.randomUUID();
9 const result = await auth.getAuthorizationUrl({ state });
10 // result.url -> redirect the user here
11 // result.codeVerifier -> store this securely until the callback

getAuthorizationUrl 返回一个 AuthorizationUrlResult,其中包含完整 URL(嵌入 PKCE code_challenge)以及您在下一步中需要的 codeVerifier

2

使用验证器交换代码

当用户被重定向回来时:

1 await auth.exchangeCode({
2 code: "CODE_FROM_CALLBACK",
3 codeVerifier: result.codeVerifier,
4 });
3

使用客户端

1 import { FrameioClient } from "frameio";
2
3 const client = new FrameioClient({ token: () => auth.getToken() });

就是这样。 刷新操作与 Web 应用程序相同 — SDK 会自动使用刷新令牌。 区别在于,刷新期间不会发送客户端密钥,因为 SPA 流专为公共客户端而设计。

codeVerifier 必须安全地存储在授权请求和代码交换之间的客户端。 在浏览器应用程序中使用 sessionStorage 或等效方式。


原生应用程序(授权代码 + PKCE)

将其用于桌面和移动应用程序。 在 Adobe Developer Console 中创建原生应用程序凭据时,Adobe 会为您分配一个格式为 adobe+<hash>://callback</hash> 的重定向 URI——您需要在操作系统级别注册您的应用程序来处理该自定义 URI 方案。 本地开发也支持环回重定向 (http://127.0.0.1:<port>/callback</port>)。 该流与 SPA 相同 — 使用 PKCE 且没有客户端密钥。

1import { NativeAppAuth } from "frameio";
2
3const auth = new NativeAppAuth({
4 clientId: "YOUR_CLIENT_ID",
5 redirectUri: "adobe+abc123def456://callback", // from your Adobe Developer Console Native App credential
6 // Also supports loopback: "http://127.0.0.1:8080/callback"
7});
8
9const { url, codeVerifier } = await auth.getAuthorizationUrl({
10 state: crypto.randomUUID(),
11});
12
13// Open system browser to `url`
14// Listen for redirect on your custom URI scheme or loopback server
15
16await auth.exchangeCode({ code: "CODE_FROM_REDIRECT", codeVerifier });
17const client = new FrameioClient({ token: () => auth.getToken() });

重定向 URI 规则

Adobe 在两个点执行重定向 URI 规则:在 Adobe Developer Console 中注册凭据时,以及当 redirect_uri 参数访问 /authorize/v2 端点时。 您在此 SDK 中传递给 redirectUri 的值必须与您在凭据上注册的其中一个“重定向 URI 模式”匹配,否则 Adobe 将重定向到凭据上的默认重定向 URI。

  • Web 应用程序SPA 凭据需要 HTTPS。
  • 原生应用程序凭据使用非 HTTPS 重定向 — 通常是 Developer Console 中显示的凭据 adobe+<hash>://callback</hash> URI。

有关您的凭据接受的确切模式,请参阅 Adobe Developer Console

Python SDK 不包含原生应用程序凭据类,因为 Python 没有用于注册自定义

URI 方案处理程序的标准方法。 TypeScript SDK 支持包括原生应用程序在内的所有四种凭据类型。


手动令牌刷新

对于 Web 应用程序、SPA 和原生应用程序流,SDK 通过 getToken() 自动刷新令牌。 如果您需要显式控制,可以直接调用 refresh()

1await auth.refresh(); // fetches a new access token using the refresh token

当您希望在关键操作之前强制刷新而不是依赖自动刷新缓冲区时,这样做非常有用。

refresh() 可用于 WebAppAuthSPAAuthNativeAppAuth 上。 如果没有可用的刷新令牌,则会抛出 ConfigurationError(即您必须先调用 exchangeCode())。 ServerToServerAuth 没有 refresh() 方法 — 而是使用 authenticate() 通过客户端凭据获取新令牌。


令牌持久性

所有身份验证类都支持 exportTokens()importTokens() 来在重新启动时持久化令牌状态。 对于 Web 应用程序、SPA 和原生应用程序流程,这尤其重要,因为访问令牌和刷新令牌默认保存在内存中 — 如果您的应用程序重新启动,用户需要重新进行身份验证,除非您对这些令牌进行持久化。 对于服务器到服务器,持久性是可选的(客户端凭据始终可以生成新令牌),但导入缓存的令牌可以避免启动时出现额外的往返。

导出和导入

1// After exchangeCode(), save the token state
2const tokenData = auth.exportTokens();
3// tokenData is: { access_token: "...", refresh_token: "...", expires_at: 1234567890.0 }
4// Save it to your database, file, or secret store
5
6// On next startup, restore it
7auth.importTokens(tokenData);
8const client = new FrameioClient({ token: () => auth.getToken() });
9// The SDK will automatically refresh if the token is near expiry

安全存储导出的令牌。 它们包含用于授权凭证 API 访问权限的访问令牌和刷新令牌。 避免将令牌写入

生产中的纯文本文件。

使用 onTokenRefreshed 自动持久化

要在每次刷新时自动持久化令牌,请使用 onTokenRefreshed 回调:

1import fs from "fs/promises";
2
3const TOKEN_FILE = "tokens.json";
4
5const auth = new WebAppAuth({
6 clientId: "...",
7 clientSecret: "...",
8 redirectUri: "...",
9 onTokenRefreshed: (tokens) => {
10 fs.writeFile(TOKEN_FILE, JSON.stringify(tokens));
11 },
12});
13
14// On startup, restore if available
15try {
16 const saved = JSON.parse(await fs.readFile(TOKEN_FILE, "utf-8"));
17 auth.importTokens(saved);
18} catch {
19 // No saved tokens — user will need to authenticate
20}

该回调接收与 exportTokens() 相同结构的数据,并在每次成功刷新令牌后触发。


撤销令牌

要注销用户并通过 Adobe IMS 使其令牌失效:

1await auth.revoke();

这会向 Adobe IMS 发出两个尽力撤销请求(一个用于访问令牌,一个用于刷新令牌,两者并行),然后清除所有本地令牌状态。对于机密客户端 (WebAppAuth),撤销请求使用 HTTP Basic Auth;对于公共客户端(SPAAuthNativeAppAuth),client_id 作为查询参数发送。 撤销错误会被记录但不会被抛出。 撤销后,用户将需要重新进行身份验证。


错误处理

所有身份验证错误都继承自 FrameioAuthError,因此您可以广泛捕获它们或处理特定用例:

1import {
2 FrameioAuthError,
3 AuthenticationError,
4 TokenExpiredError,
5 ConfigurationError,
6 NetworkError,
7 RateLimitError,
8} from "frameio";
9
10try {
11 await auth.exchangeCode("...");
12} catch (error) {
13 if (error instanceof TokenExpiredError) {
14 // The refresh token has expired; redirect the user to sign in again
15 } else if (error instanceof AuthenticationError) {
16 // Token exchange failed
17 console.error(`Error: ${error.errorCode} - ${error.errorDescription}`);
18 } else if (error instanceof NetworkError) {
19 // Timeout or connection failure (after retries)
20 } else if (error instanceof RateLimitError) {
21 // 429 from Adobe IMS; retry after error.retryAfter seconds
22 } else if (error instanceof FrameioAuthError) {
23 // Catch-all for any other auth error
24 }
25}

错误参考

例外引发的时间
ConfigurationError配置缺失或无效(例如 clientId 为空、非 HTTPS 重定向 URI、非 HTTPS imsBaseUrl
AuthenticationErrorAdobe IMS 拒绝令牌交换或刷新(具有 .errorCode.errorDescription
TokenExpiredError刷新令牌本身已过期;用户必须重新进行身份验证
NetworkErrorHTTP 超时或完成所有重试后仍出现连接故障
RateLimitErrorAdobe IMS 返回 429;请查看 .retryAfter 获取退避指导
PKCEError可供消费者在 PKCE 流中使用;不会由 SDK 内部抛出

在生产中处理过期的刷新令牌

对于 Web 应用程序、SPA 和原生应用程序流,刷新令牌最终会过期。当这种情况发生时,getToken() 会引发 TokenExpiredError。 您应该捕获这个错误,并再次将用户重定向到授权流。

1import { TokenExpiredError } from "frameio";
2
3try {
4 const client = new FrameioClient({ token: () => auth.getToken() });
5 const assets = await client.files.list({ projectId: "..." });
6} catch (error) {
7 if (error instanceof TokenExpiredError) {
8 // Clear persisted tokens and redirect user to login
9 await auth.revoke();
10 return res.redirect("/login");
11 }
12}

配置参考

这些参数具有合理的默认值,很少需要设置。 如果您确实需要自定义行为(指向暂存 IMS、注入自定义 fetch、微调超时或连接日志记录器),请在构造授权类时将其中任何一个作为可选参数传递:

参数默认描述
scopes特定于流的默认值以空格分隔的 OAuth 权限范围。 S2S 默认为 openid AdobeID frame.s2s.all;面向用户的流默认为 openid email profile offline_access additional_info.roles
imsBaseUrlhttps://ims-na1.adobelogin.comAdobe IMS 基础 URL。 针对暂存或非生产环境的覆盖设置。 必须使用 HTTPS。
fetchglobalThis.fetch用于代理、mTLS 或自定义 HTTP 处理的自定义 fetch 实施。
timeout30000令牌端点调用的 HTTP 请求超时时间(以毫秒为单位)。
maxRetries2瞬态故障(5xx、超时)的最大重试次数。 速率限制重试 (429) 单独跟踪。
refreshBuffer60令牌过期时间前触发主动刷新的秒数。
onTokenRefreshedundefined每次成功刷新令牌后触发的回调。 接收包含 access_tokenrefresh_tokenexpires_at 的对象。
logger无操作(静默)采用 debuginfowarnerror 方法的记录器实例(例如 consolepinowinston)。

暂存环境

通过重写 imsBaseUrl 指向暂存 Adobe IMS 实例。 如果您需要以编程方式引用生产值,SDK 还可以导出 DEFAULT_IMS_BASE_URL (https://ims-na1.adobelogin.com)。

1const auth = new ServerToServerAuth({
2 clientId: "...",
3 clientSecret: "...",
4 imsBaseUrl: "https://ims-na1-stg1.adobelogin.com",
5});

自定义获取

对于代理支持或自定义 TLS 配置:

1import { ProxyAgent } from "undici";
2
3const proxyAgent = new ProxyAgent("http://corporate-proxy:8080");
4
5const auth = new ServerToServerAuth({
6 clientId: "...",
7 clientSecret: "...",
8 fetch: (url, init) => fetch(url, { ...init, dispatcher: proxyAgent }),
9});

并发安全性

TypeScript SDK 可安全用于并发使用。 当多个 getToken() 调用同时发生且需要刷新时,只会触发一个刷新请求。 其他调用会等待相同的 promise,最终接收相同的结果。 无需外部锁定。 这种去重技术利用 JavaScript 的单线程事件循环和共享的 Promise — 如果刷新已在进行中,并发调用者会加入其中,而不是启动第二个请求。 如果在刷新操作期间调用 revoke(),刷新会因 AuthenticationError 被拒绝,同时令牌也会保持清除状态,撤销始终优先。