Frame.io TypeScript SDK — 認証ガイド

このガイドでは、Frame.io TypeScript SDKframeio)を使用してFrame.io APIで認証する方法について説明します。Frame.io V4 APIはAdobe Identity Management Service(IMS)、アドビのOAuth 2.0アイデンティティプラットフォームを使用します。

これはTypeScript/JavaScript開発者向けのスタンドアロンリファレンスです。以下のすべてのコード例とフローはframeioパッケージのみを対象としています。


TypeScript SDKの認証タイプ

TypeScript SDKは、4つのOAuth認証クラスと、トークンを直接使用する方法をサポートしています。

方法ユースケースユーザー操作?クライアントシークレットが必要?
静的トークン簡易スクリプト、テスト、または既にトークンを持っている場合いいえいいえ
Server-to-Serverバックエンドサービス、cronジョブ、自動化いいえはい
Web Appサーバーサイドアプリ(Express、Fastify、Next.js)はいはい
SPA (PKCE)シークレットを保存できないブラウザーアプリはいいいえ
Native App (PKCE)カスタムURIスキームリダイレクトを持つデスクトップ/モバイルアプリはいいいえ

Server-to-Serverは、ユーザーの操作なしでアプリをサービスアカウントとして動作させます。これは、Adobe Admin Consoleで管理されているFrame.io V4アカウントでのみ利用できます。

Web AppSPAは、アプリを特定のユーザーとして動作させます。どちらも内部的にAdobe IMSを使用します。ユーザーがアプリを認可し、SDKは結果のコードをトークンと交換します。TypeScript SDKは、IMSの/authorize/v2および/token/v3フローを処理します。Web Appにはクライアントシークレットが必要ですが、SPAは代わりにPKCEを使用します。

Native AppはSPAと同じPKCEフローに従いますが、Adobeがネイティブアプリ資格情報に割り当てるadobe+<hash>://callbackリダイレクトURIを使用します。これにより、認可後にOSレベルでアプリケーションがリダイレクトをインターセプトできます。


サービスアカウントユーザー

Server-to-Server認証を使用する場合、アプリケーションはサービスアカウントユーザーとして動作します。これは、サービスに代わってアクションを実行できる独特なアカウントタイプです。これらはFrame.ioの他のユーザーに表示されます。サービスアカウントがアクションを実行すると、その名前がUIに表示されます。

Adobe Admin ConsoleDeveloper Consoleを通じてサービスアカウントアクセスを付与および取り消すことができます。サービスアカウント名はFrame.io UIから管理されます。デフォルトでは、最初のS2S接続はService Account User、2番目はService Account User 2、というように名前が付けられます。

詳細については、Frame.ioサーバー間サポートを使用した設定の自動化を参照してください。


クイックスタート

前提条件

  1. Adobe Developer Consoleからの資格情報

    • Client ID — すべてのOAuthフローに必要
    • Client Secret — Server-to-ServerおよびWeb Appフローに必要
    • Redirect URI — Web App、SPA、Native Appフローに必要。Adobeプロジェクトに登録する必要があります
  2. SDKをインストール:

$npm install frameio

方法の選択

  • ユーザーが関与しない場合: Server-to-ServerServerToServerAuth)を使用します。
  • ユーザーが関与し、シークレットを保存できる場合: Web AppWebAppAuth)を使用します。
  • ユーザーが関与するが、シークレットを保存できない場合: ** ブラウザーアプリにはSPA**(SPAAuth)を、デスクトップ/モバイルアプリにはNative AppNativeAppAuth)を使用します。

アクセストークン

既にアクセストークンがある場合(他のOAuthシステムまたは以前の交換から、例えばAPI Explorer経由)、直接渡すことができます:

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

これは最もシンプルなアプローチですが、トークンは最終的に期限切れになり、SDKは自動的に更新しません。

レガシーデベロッパートークン

Adobe Admin Consoleでまだ管理されていないV4移行アカウントの場合、Frame.io developer siteからのレガシーデベロッパートークンを引き続き使用できます。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フローのいずれかを使用することをお勧めします。詳細については、移行ガイドを参照してください。


Server-to-Server(クライアント資格情報)

ユーザーの操作なしに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にリダイレクト

1import { WebAppAuth } from "frameio";
2import crypto from "crypto";
3
4const auth = new WebAppAuth({
5 clientId: "YOUR_CLIENT_ID",
6 clientSecret: "YOUR_CLIENT_SECRET",
7 redirectUri: "https://yourapp.com/callback",
8});
9
10// CSRF攻撃を防ぐために暗号的にランダムなstate値を生成
11const state = crypto.randomBytes(32).toString("hex");
12
13const authorizationUrl = auth.getAuthorizationUrl({ state });
14// `state`をユーザーのセッションに保存し、`authorizationUrl`にリダイレクトします
2

コールバックを処理

Adobe IMSがユーザーをredirectUriにリダイレクトした際、codestateパラメーターを抽出します。stateが保存したものと一致することを確認してから、codeをトークンと交換します:

1// コールバックハンドラー内(例:Expressルート):
2await auth.exchangeCode(req.query.code as string);

これにより認証コードをアクセストークンとリフレッシュトークンに交換し、両方を内部に保存します。

3

クライアントを使用

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

これで完了です。この時点から、getToken()がトークンのライフサイクルを自動的に管理します。アクセストークンの有効期限が近づくと、SDKはリフレッシュトークンを使用して新しいトークンを取得します。ユーザーの操作は不要です。

完全なExpressの例

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を生成

1import { SPAAuth } from "frameio";
2
3const auth = new SPAAuth({
4 clientId: "YOUR_CLIENT_ID",
5 redirectUri: "https://yourapp.com/callback",
6});
7
8const state = crypto.randomUUID();
9const result = await auth.getAuthorizationUrl({ state });
10// result.url -> ユーザーをここにリダイレクト
11// result.codeVerifier -> コールバックまで安全に保存

getAuthorizationUrlは、完全なURL(PKCE code_challengeが埋め込まれた)と次のステップで必要なcodeVerifierを含むAuthorizationUrlResultを返します。

2

検証機能付きでコードを交換

ユーザーがリダイレクトされた際:

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

クライアントを使用

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

これで完了です。リフレッシュはWebアプリと同じように動作します — SDKがリフレッシュトークンを自動的に使用します。違いは、SPAフローがパブリッククライアント向けに設計されているため、リフレッシュ時にクライアントシークレットが送信されないことです。

codeVerifierは認証リクエストとコード交換の間、クライアント側で安全に保存する必要があります。ブラウザーアプリではsessionStorageまたは同等のものを使用してください。


ネイティブアプリ(認証コード + PKCE)

デスクトップおよびモバイルアプリケーションに使用します。Adobe Developer Consoleでネイティブアプリ資格情報を作成すると、Adobeはadobe+<hash>://callbackの形式のリダイレクトURIを割り当てます — OSレベルでそのカスタムURIスキームを処理するようにアプリケーションを登録します。ループバックリダイレクト(http://127.0.0.1:<port>/callback)もローカル開発でサポートされています。フローは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は2つのポイントでリダイレクトURIルールを適用します:Adobe Developer Consoleで資格情報を登録するときと、redirect_uriパラメーターが/authorize/v2エンドポイントにヒットするときです。このSDKでredirectUriに渡す値は、資格情報に登録した「リダイレクトURIパターン」のいずれかと一致する必要があります — そうでなければ、Adobeは代わりに資格情報のデフォルトリダイレクトURIにリダイレクトします。

  • Web AppおよびSPA資格情報にはHTTPSが必要です。
  • Native App資格情報は非HTTPSリダイレクトを使用します — 通常はDeveloper Consoleで資格情報に表示されるadobe+<hash>://callback URIです。

資格情報で受け入れられる正確なパターンについては、Adobe Developer Consoleを参照してください。

Python SDKにはNative App資格情報クラスは含まれていません。Pythonにはカスタム URIスキームハンドラーを登録する標準的な方法がないためです。TypeScript SDKはNative Appを含むすべての4つの資格情報タイプをサポートしています。


手動トークン更新

Web App、SPA、およびNative Appフローでは、SDKはgetToken()を介してトークンを自動的に更新します。明示的な制御が必要な場合は、refresh()を直接呼び出すことができます:

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

これは、自動更新バッファーに依存するのではなく、重要な操作の前に強制的に更新したい場合に便利です。

refresh()WebAppAuthSPAAuth、およびNativeAppAuthで利用できます。リフレッシュトークンが利用できない場合(つまり、最初にexchangeCode()を呼び出す必要がある場合)、ConfigurationErrorをスローします。ServerToServerAuthにはrefresh()メソッドがありません — 代わりにクライアント資格情報を介して新しいトークンを取得するためにauthenticate()を使用します。


トークンの永続化

すべての認証クラスは、再起動間でトークン状態を永続化するためにexportTokens()importTokens()をサポートしています。Webアプリ、SPA、ネイティブアプリのフローでは、アクセストークンとリフレッシュトークンがデフォルトでメモリに保存されるため、これは特に重要です。アプリケーションが再起動すると、トークンを永続化しない限り、ユーザーは再認証する必要があります。Server-to-Serverでは、永続化はオプションです(クライアント資格情報は常に新しいトークンを発行できます)が、キャッシュされたトークンをインポートすることで、起動時の余分なラウンドトリップを回避できます。

エクスポートとインポート

1// exchangeCode()の後にトークン状態を保存
2const tokenData = auth.exportTokens();
3// tokenData is: { access_token: "...", refresh_token: "...", expires_at: 1234567890.0 }
4// データベース、ファイル、またはシークレットストアに保存
5
6// 次回起動時に復元
7auth.importTokens(tokenData);
8const client = new FrameioClient({ token: () => auth.getToken() });
9// SDKは、トークンの有効期限が近い場合は自動的に更新します

エクスポートしたトークンは安全に保存してください。これらには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// 起動時に、利用可能な場合は復元
15try {
16 const saved = JSON.parse(await fs.readFile(TOKEN_FILE, "utf-8"));
17 auth.importTokens(saved);
18} catch {
19 // 保存されたトークンがありません — ユーザーは認証する必要があります
20}

コールバックはexportTokens()と同じ形式を受け取り、トークンの更新が成功するたびに実行されます。


トークンの取り消し

ユーザーをログアウトし、Adobe IMSでトークンを無効にするには:

1await auth.revoke();

これにより、Adobe IMSに対して2つのベストエフォート取り消しリクエストが並行して実行されます。1つはアクセストークン用、もう1つはリフレッシュトークン用で、すべてのローカルトークン状態がクリアされます。機密クライアント(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 // リフレッシュトークンの有効期限が切れています。ユーザーを再度ログインにリダイレクトしてください
15 } else if (error instanceof AuthenticationError) {
16 // トークン交換に失敗しました
17 console.error(`Error: ${error.errorCode} - ${error.errorDescription}`);
18 } else if (error instanceof NetworkError) {
19 // タイムアウトまたは接続エラー(再試行後)
20 } else if (error instanceof RateLimitError) {
21 // Adobe IMSから429エラー。error.retryAfter秒後に再試行してください
22 } else if (error instanceof FrameioAuthError) {
23 // その他の認証エラーをすべてキャッチ
24 }
25}

エラーリファレンス

例外発生するタイミング
ConfigurationError設定が不足または無効(例:空のclientId、非HTTPSリダイレクトURI、非HTTPS imsBaseUrl
AuthenticationErrorトークン交換または更新がAdobe IMSによって拒否された(.errorCode.errorDescriptionを持つ)
TokenExpiredErrorリフレッシュトークン自体が期限切れ。ユーザーは再認証が必要
NetworkErrorすべての再試行後のHTTPタイムアウトまたは接続失敗
RateLimitErrorAdobe IMSが429を返した。バックオフガイダンスについては.retryAfterを確認
PKCEErrorPKCEフローでの利用者使用に利用可能。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 // 永続化されたトークンをクリアし、ユーザーをログインにリダイレクト
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_URLhttps://ims-na1.adobelogin.com)もエクスポートしており、プログラム的に本番環境の値を参照する必要がある場合に使用できます。

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

カスタムfetch

プロキシサポートやカスタム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()呼び出しが同時に発生し、更新が必要な場合、更新リクエストは1つだけ実行されます。他の呼び出しは同じプロミスを待機し、同じ結果を受け取ります。外部ロックは必要ありません。

この重複除外は、JavaScriptのシングルスレッドイベントループと共有Promiseを使用します — 更新が既に実行中の場合、同時呼び出し元は2番目のリクエストを開始する代わりにそれに参加します。

更新の実行中にrevoke()が呼び出された場合、更新はAuthenticationErrorで拒否され、トークンはクリアされたままになります — 取り消しが常に優先されます。