logo
Development
検索
プッシュ連携(ID ハンドシェイク / Push Handoff)

プッシュ連携(ID ハンドシェイク / Push Handoff)

シナリオ:ユーザーが ワークスペース → 拡張 ページで開発者の拡張アプリをクリックすると、プラットフォームがユーザー ID を ?wsa=<JWT> として対象拡張アプリのランディングページへプッシュします。開発者はランディングページで消費し、バックエンドで検証した後、自前のセッションに置き換えます。

本モードでは、開発者はプラットフォームの署名エンドポイントを 呼び出す必要はありません——それはユーザーがクリックしたときにワークスペースのフロントエンドが能動的に呼び出すものです。開発者は「受け取って検証する」だけを担います。

エンドツーエンドのシーケンス

loading...
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); }
                      
                      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 を消去する
                      
                      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 }); } });
                      
                      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.replaceStatewsa を消去する(consumeHandoff はデフォルトで実施済み)
  • 自前のセッションに置き換え、以降の XHR/fetch/img で wsa透過的に渡さない
  • 「ユーザーが直接アクセスし、wsa がない」分岐を処理する(ログインへ誘導するか匿名にする)
  • 検証失敗時は WsaVerificationError.code に応じて読みやすいメッセージを出す

セキュリティの詳細と多言語検証:トークン検証とセキュリティ。「アプリ内ログインボタン」に変えたい場合:プルログイン