プッシュ連携(ID ハンドシェイク / Push Handoff)
シナリオ:ユーザーが ワークスペース → 拡張 ページで開発者の拡張アプリをクリックすると、プラットフォームがユーザー ID を ?wsa=<JWT> として対象拡張アプリのランディングページへプッシュします。開発者はランディングページで消費し、バックエンドで検証した後、自前のセッションに置き換えます。
本モードでは、開発者はプラットフォームの署名エンドポイントを 呼び出す必要はありません——それはユーザーがクリックしたときにワークスペースのフロントエンドが能動的に呼び出すものです。開発者は「受け取って検証する」だけを担います。
エンドツーエンドのシーケンス
sequenceDiagram
autonumber
actor User as 拡張アプリにアクセスするユーザー
participant GB as GPTBots プラットフォーム
participant FE as 拡張アプリ-ランディングページ
participant BE as 拡張アプリ-バックエンド
User->>GB: アプリアイコンをクリック(POST sign-token)
GB->>GB: クリックした人がその workspace メンバーか検証
GB->>GB: シークレットで wsa に署名<br/>(5 分間の JWT、aud=拡張アプリの host)
GB->>FE: app_home_url?wsa=<JWT> を開く
Note over FE: consumeHandoff()<br/>?wsa= を読み、拡張アプリバックエンドへ POST
FE->>BE: POST /session/exchange { wsa }
BE->>BE: verifyWsa() → identity
BE->>BE: 自前の session を確立
BE-->>FE: session を返す(Set-Cookie)
Note over FE: history.replaceState で ?wsa= を消去
プラットフォームがリダイレクト URL を生成するルール(開発者が実装する必要はありません):
- 登録情報から
app_home_urlで対象拡張アプリを 厳密に一致 させます(未登録の URL は一律署名を拒否し、ID の漏洩を防ぎます)。 - クリックした人が確かにそのワークスペース(
workspace_id)のメンバーであることを検証します。 app_home_urlにすでに query が付いている場合は、&でwsaを連結します。wsaの値はすでに URL エンコードされているため、読み取った後に手動でデコードする必要はありません。
フロントエンド:ランディングページの wsa を消費する
SDK を使う(推奨)
import { consumeHandoff } from '@gptbots/workspace-extension-sdk';
try {
const identity = await consumeHandoff({
exchangeUrl: '/session/exchange', // あなた自身のバックエンド検証インターフェース
// search: location.search, // デフォルトで location.search を読む
// paramName: 'wsa', // デフォルトのパラメータ名は wsa
// strip: true, // デフォルトで成功後に URL 内の wsa を消去
});
bootYourApp(identity);
} catch (e) {
// wsa がない(ユーザーが直接アクセス)、またはバックエンド検証失敗
showLoginOrError(e);
}
consumeHandoff は 4 つのことを行います:① ?wsa= を読む ② { wsa } を exchangeUrl へ POST する ③ 成功後 history.replaceState で ?wsa= を消去する ④ バックエンドが返した identity を返す。
消去のタイミング:トークンは 交換に成功した後にのみ URL から消去されます。これは一時的な失敗の際にリロードで再試行できるようにするためです。これは 5 分間の使い捨てトークンです。失敗時にも即座に消去したい場合は、
catchの後に手動でstripHandoffToken()を呼び出せます。
セッションを作らず、ID を読むだけ(receive-only)
import { readHandoffToken, stripHandoffToken } from '@gptbots/workspace-extension-sdk';
const token = readHandoffToken(); // 純粋関数、生の JWT 文字列または null を返す
if (token) {
// それでも token をバックエンドに送り verifyWsa してから内容を信頼することを推奨(フロントエンドで JWT を解析して信頼できる ID として扱わないこと)
await fetch('/telemetry/visit', { method: 'POST', body: JSON.stringify({ wsa: token }) });
}
stripHandoffToken(); // いずれにせよ URL 内の wsa を消去する
⚠️ フロントエンドで JWT を展開しても、それを信頼できる ID として扱わないでください。JWT の署名はシークレットがあってはじめて検証でき、そのシークレットはバックエンドにしかありません。フロントエンドでの解析は「非セキュアな」表示用プレースホルダーにしか使えず、あらゆる認可判断は バックエンドの
verifyWsaの結果 を基準にしなければなりません。
バックエンド:wsa を検証する
3.1 SDK を使う(推奨)
import { verifyWsa, WsaVerificationError } from '@gptbots/workspace-extension-verify';
app.post('/session/exchange', (req, res) => {
try {
const identity = verifyWsa(req.body.wsa, {
secret: process.env.EXTENSION_APP_SECRET, // Tier2 の per-app シークレット または Tier1 の共有シークレット
audience: 'app.example.com', // 拡張アプリの host、aud と一致する必要がある
// issuer: 'gptbots-workspace', // デフォルト
// leewaySeconds: 30, // 時計のずれの許容、デフォルト 30s
// algorithms: ['HS256'], // デフォルト
});
// マルチテナント分離:リクエストを identity.workspaceId の配下に振り分ける
const sid = createSession(identity);
res.cookie('sid', sid, { httpOnly: true, secure: true, sameSite: 'lax' });
res.json(identity);
} catch (e) {
const code = e instanceof WsaVerificationError ? e.code : 'Error';
res.status(401).json({ code });
}
});
SDK を使わない場合
wsa は標準的な HS256 JWT なので、どの言語の JWT ライブラリでも検証できます。Java / Node / Python の例は 05-トークン検証とセキュリティ §3 を参照してください。SDK を使う使わないにかかわらず、署名 / iss / aud / exp の 4 項目はすべて検証しなければなりません。
拡張アプリの登録(シークレットの取得)
ワークスペースの OWNER/ADMIN:ワークスペース → スペース管理 → 拡張アプリ → 追加 で、次を入力します:
| フィールド | 説明 |
|---|---|
| アプリ名 | 表示名。切り詰めを避けるため全角 12 文字以下を推奨 |
| アプリアイコン | 正方形のアイコン。128×128 以上を推奨 |
| アプリ入口 URL | あなたの app_home_url。一意キー として、署名時に完全な文字列で厳密に一致させる |
| 認証モード | workspace_account(ID の受け渡しが必要)を選ぶ |
送信後、App Secret を平文で一度だけ表示 します。すぐにコピーして保存してください(閉じた後はローテーションのみ可能)。
ランディングページのチェックリスト
-
?wsa=を受け取ったら、まず拡張アプリのバックエンドサービスへ POST して検証し、それから内容を信頼する - 検証を通過したら 即座に
history.replaceStateでwsaを消去する(consumeHandoffはデフォルトで実施済み) - 自前のセッションに置き換え、以降の XHR/fetch/img で
wsaを 透過的に渡さない - 「ユーザーが直接アクセスし、
wsaがない」分岐を処理する(ログインへ誘導するか匿名にする) - 検証失敗時は
WsaVerificationError.codeに応じて読みやすいメッセージを出す
セキュリティの詳細と多言語検証:トークン検証とセキュリティ。「アプリ内ログインボタン」に変えたい場合:プルログイン。
