logo
Development
検索
05 · トークン検証とセキュリティ

05 · トークン検証とセキュリティ

プッシュであれプルであれ、最終的に開発者は一枚の wsa(HS256 JWT)を受け取ります。本編はすべての接続側にとって必読 です——検証が甘い = ID が偽造可能ということです。

必須検証チェックリスト

wsa を取得したら、拡張アプリのバックエンドサービスで 順番に検証します(verifyWsa はすべてを内蔵済み):

  1. 署名:プラットフォームが配布/登録で得たシークレットで HS256 検証します。失敗 → 拒否。
  2. exp:現在時刻 ≤ exp(わずかな leeway を含む)。exp必ず存在 しなければなりません——exp のないトークンは直接拒否すべきです(さもなければ永久に期限切れにならないのと同じです)。
  3. issgptbots-workspace と一致しなければなりません。
  4. aud対象拡張アプリの host と一致しなければなりません。これは「トークンを盗んで別のアプリに打ち込む」ことを防ぐ要の防衛線であり、省略できません。
  5. iat / nbf(存在する場合):未来(leeway を超える)であってはなりません。「発行時刻を遠い未来に設定した」トークンが長期間使えることを防ぎます。
  6. workspace_id によるテナント分離:拡張が複数ワークスペースの分離を行っている場合、リクエストをその workspace_id の配下に振り分け、テナント跨ぎのアクセスを禁止します。
  7. ランディング後すぐに URL 内の wsa / code を消去history.replaceState で、ユーザーが URL をコピーしたときにトークンまで一緒に共有してしまうのを防ぎます。
  8. 自前のセッションを確立:拡張アプリ自身の session/cookie に置き換え、以降のリクエストで wsa に依存 しない ようにします——5 分で期限切れになります。

公式 SDK の verifyWsa は、exp の存在を必須とし、iat/nbf の未来方向のずれを検証し、定数時間での署名比較とアルゴリズムのホワイトリスト(デフォルトは HS256 のみ、alg 混同攻撃を防止)を行います。これを使えば 1〜5 は自動的に満たされます。


SDK での検証を推奨

import { verifyWsa, WsaVerificationError } from '@gptbots/workspace-extension-verify'; try { const id = verifyWsa(wsa, { secret: process.env.EXTENSION_APP_SECRET, audience: 'app.example.com', }); // id: { accountId, role, workspaceId, username?, email?, avatar?, appName?, issuedAt?, expiresAt? } } catch (e) { if (e instanceof WsaVerificationError) { // e.code: InvalidToken | InvalidSignature | Expired | NotYetValid // | WrongIssuer | WrongAudience | MissingClaim | UnsupportedAlgorithm } }
                      
                      import { verifyWsa, WsaVerificationError } from '@gptbots/workspace-extension-verify';

try {
  const id = verifyWsa(wsa, {
    secret: process.env.EXTENSION_APP_SECRET,
    audience: 'app.example.com',
  });
  // id: { accountId, role, workspaceId, username?, email?, avatar?, appName?, issuedAt?, expiresAt? }
} catch (e) {
  if (e instanceof WsaVerificationError) {
    // e.code: InvalidToken | InvalidSignature | Expired | NotYetValid
    //       | WrongIssuer | WrongAudience | MissingClaim | UnsupportedAlgorithm
  }
}

                    
このコードブロックをポップアップで表示

完全なオプションとエラーコードは SDK APIリファレンス を参照してください。

SDK を使わない検証(多言語)

wsa は標準的な HS256 JWT なので、どの JWT ライブラリでも検証できます。必ず iss / aud 検証を明示的に有効化し、algorithms=['HS256'] に固定してください。

Java(auth0 java-jwt)

JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SHARED_SECRET)) .withIssuer("gptbots-workspace") .withAudience("app.example.com") .acceptLeeway(30) // 30s の時計のずれを許容 .build(); DecodedJWT jwt = verifier.verify(wsaParam); String userId = jwt.getSubject(); String workspaceId = jwt.getClaim("workspace_id").asString(); String role = jwt.getClaim("role").asString(); String username = jwt.getClaim("username").asString(); // null の可能性あり String email = jwt.getClaim("email").asString(); // null の可能性あり
                      
                      JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SHARED_SECRET))
    .withIssuer("gptbots-workspace")
    .withAudience("app.example.com")
    .acceptLeeway(30) // 30s の時計のずれを許容
    .build();

DecodedJWT jwt = verifier.verify(wsaParam);
String userId      = jwt.getSubject();
String workspaceId = jwt.getClaim("workspace_id").asString();
String role        = jwt.getClaim("role").asString();
String username    = jwt.getClaim("username").asString();  // null の可能性あり
String email       = jwt.getClaim("email").asString();     // null の可能性あり

                    
このコードブロックをポップアップで表示

Node.js(jsonwebtoken)

const jwt = require('jsonwebtoken'); const payload = jwt.verify(wsaParam, SHARED_SECRET, { algorithms: ['HS256'], issuer: 'gptbots-workspace', audience: 'app.example.com', clockTolerance: 30, }); const { sub: userId, workspace_id, role, username, email, avatar } = payload;
                      
                      const jwt = require('jsonwebtoken');

const payload = jwt.verify(wsaParam, SHARED_SECRET, {
  algorithms: ['HS256'],
  issuer: 'gptbots-workspace',
  audience: 'app.example.com',
  clockTolerance: 30,
});
const { sub: userId, workspace_id, role, username, email, avatar } = payload;

                    
このコードブロックをポップアップで表示

Python(PyJWT)

import jwt payload = jwt.decode( wsa_param, SHARED_SECRET, algorithms=["HS256"], issuer="gptbots-workspace", audience="app.example.com", leeway=30, ) user_id = payload["sub"] workspace_id = payload["workspace_id"] role = payload["role"]
                      
                      import jwt

payload = jwt.decode(
    wsa_param,
    SHARED_SECRET,
    algorithms=["HS256"],
    issuer="gptbots-workspace",
    audience="app.example.com",
    leeway=30,
)
user_id      = payload["sub"]
workspace_id = payload["workspace_id"]
role         = payload["role"]

                    
このコードブロックをポップアップで表示

シークレットのエンコードに関する注意:HS256 はシークレット文字列をそのまま UTF-8 バイトとして HMAC key に使います。Tier 2 の per-app シークレットは wext_+64 hex のような形式ですが、文字列をそのまま シークレットとして渡してください(改めて hex デコードしないこと)。プラットフォームの署名側と一致させます。

セキュリティ要件(一つずつ実施)

  1. シークレットがセキュリティモデルのすべて。現在の HS256 は対称鍵です:一度漏洩すれば、誰でも任意のワークスペースユーザーの ID を偽造して接続先の拡張アプリに打ち込めます。バックエンドにのみ配置(環境変数/KMS)し、フロントエンドコード、git リポジトリ、ログ、クライアント設定に絶対に書き込まないでください。
  2. JWT が URL に露出する範囲(プッシュのみ):query string はブラウザ履歴、Web サーバーのアクセスログ、CDN キャッシュログ、Referer に記録されます。たとえ記録されても、5 分以内に入手されれば依然として悪用可能です。ランディング後すぐに history.replaceState で消去 してください(consumeHandoff はデフォルトで実施済み)。プル(M-Auth)は元来 wsa を URL に入れないため、より安全です。
  3. wsa を子リソースに透過的に渡さない。自前のセッションに置き換えた後、以降の XHR/fetch/img には一切生の wsa を付けないでください。さもなければ各子リソースの Referer に出現します。
  4. 時計の同期。HS256 は exp を厳密に判定するため、サーバーの時計は NTP 同期が必要です。例では 30s の leeway で小幅なずれを許容していますが、分単位まで広げないでください。
  5. role はデフォルト権限マッピングとしてのみ。それはそのワークスペースにおけるユーザーのロールを反映するだけで、あなたのアプリ内の権限を表しません。あなたのアプリは独自の権限モデルを維持します。
  6. マルチテナント分離。常に workspace_id をデータ分離キーとして使い、A ワークスペースのユーザーが B のデータを読めないようにします。
  7. シークレットのローテーション/失効の緊急対応:スペース管理でアプリの「シークレットをローテーション」すると、新しいシークレットが生成され一度だけ表示されます。その後、新しいシークレットでバックエンドを再設定してください。

5. フロントエンドの信頼境界(重要)

  • フロントエンドは表示用プレースホルダーのみを担い、認可判断はしない。フロントエンドは base64 で JWT の payload を展開できますが、シークレットがなければ署名検証できません——誰でも「正しそうに見える」payload を偽造できます。
  • 「この人は誰か、何かをしてよいか」というあらゆる判断は、必ず 拡張アプリバックエンドの verifyWsa の結果を基準にしなければなりません。
  • したがって標準的な戦略は次のとおりです:フロントエンドが wsa/code を拡張アプリバックエンドに渡す → バックエンドが検証 → バックエンドがセッションを確立 → フロントエンドはこのセッションだけを信頼する。

次のステップ:完全な API は SDK APIリファレンス を参照。問題が発生した場合は トラブルシューティング を参照してください。