ANYSCAPE Portal / Public guide

別アプリをPortalへ接続する

PortalのGoogleログインを入口にしながら、各アプリが自分のセッションとアクセス制御を持つための標準手順です。

Portalリンクだけでは認証になりません

アプリカードを非表示にしても直接URLは開けます。別アプリ自身が、未認証アクセスを拒否またはPortalログインへ転送してください。

1

接続前に決める情報

実装を始める前に、次の情報をPortal管理者へ共有してください。

  • アプリ名、本番URL、オーナーまたは担当チーム、カテゴリ
  • 許可するドメイン・メールアドレス・グループ
  • データの機密度とデプロイ先
  • 公開を維持するルートと、ログイン必須にするルート
  • 標準外の認証方式を使う場合は、そのアクセスモデルとOAuth callback URL

セキュリティ項目が未確定の場合は推測で埋めず、manifestへneeds-confirmation と記録します。

2

Portal側で行う作業

  1. broker registryへアプリID、secret環境変数名、production/localそれぞれの固定callback URLをallowedCallbackUrls として登録する。許可するpathは/auth/portal/callback だけとする。 metadataの allowedReturnOrigins はdeprecatedな旧consumer向け互換fieldであり、 新規consumerは allowedCallbackUrls を使用する。
  2. アプリ専用の十分にランダムな共有secretを生成し、Portal側のAUTH_BROKER_{APP}_SECRET と別アプリ側のPORTAL_BROKER_SECRET に同一のアプリ専用secretを安全に設定する。 secretの値はコード、manifest、URL、文書へ掲載しない。
  3. Portal dashboardへアプリカードを追加し、接続manifestを作成する。
  4. broker metadataでアプリがconfiguredと表示されることを確認する。

tokenのissuer claimは anyscape-portal です。表示名やURLをissuerとして扱わないでください。

3

別アプリ側で行う作業

  1. /auth/portal へ入る前の元の保護ページをstateと紐付け、server-side storeまたは 署名・暗号化した HttpOnly cookieへ短時間だけ保存する。保存できる値は 同一originの相対path+queryだけとし、/ 始まり、先頭二重slashは禁止、 scheme・hostを含む値は禁止する。callback成功後に一回だけ消費し、欠落・不正・再利用時は 安全なdefaultへfallbackする。外部URLを受け取るopen redirectを実装しない。
  2. /auth/portal でCSPRNGから128bit以上の予測不能な一回限りのstateを生成し、短時間だけHttpOnly、Secure、SameSite=Lax、Path=/auth/portal/callback、短い Max-Age を設定したcookieへ保存する。 callback URLにも同じstateを付け、次の形式でPortalへ遷移させる。
    /api/auth/broker/start?app=<registered-id>&returnTo=<URL-encoded callback URL>

    app はPortalへ登録したアプリIDで、tokenの aud と一致します。returnTo は常にstate付きの固定callback URL (/auth/portal/callback)をencodeした値です。元の保護ページURLをPortalへ渡してはいけません。 protocol・host・port・pathnameはPortalの allowedCallbackUrls と完全一致し、 新規実装のqueryにはstateだけを付けられます。現行方式ではquery keyは重複なしのstate 1個だけです。 値はCSPRNG 128bit以上を想定した22〜128文字のpaddingなしbase64urlとし、未知key、空値、不正文字、credentials、hashは禁止です。 既存アプリとの互換用に、重複なしの next 1個だけを持つcallbackも受け付けます。next は / から始まり、先頭二重slashでは始まらない同一originの相対path+queryだけに限定し、 絶対URL、scheme、host、fragmentは拒否されます。このlegacy next方式はdeprecatedで、新規アプリはstate方式を使ってください。

  3. callbackへtokenが到着した時点から、署名検証前後を問わずtoken全文をログ、APM、例外へ出さない。 アプリ本体だけでなくnginx、proxy、hosting、APMでも/auth/portal/callback のcallback query stringをredactionし、アクセスログやtraceへ残さない。
  4. /auth/portal/callback で、queryのstateとcookieのstateをタイミング安全な方法で比較し、一致したstateを即座に消費する。不一致・欠落・再利用はtoken検証前に拒否する。 stateの消費だけではtoken replayを防止できません。stateはログイン要求との対応を確認し、tokenの一回消費は次のreplay storeで保証します。
  5. 署名は共有secretを用いた HMAC-SHA256 で検証し、issuer、audience、email、発行・失効時刻をすべて確認する。 tokenのwire formatは次のとおりです。

    payload = paddingなしbase64url(UTF-8(JSON.stringify(claims)))

    signature = paddingなしbase64url(HMAC-SHA256(secret, encoded payload文字列))

    token = payload + "." + signature

    HMACの入力はdecode後のJSON bytesではなく、tokenの先頭segmentそのものです。署名を先に検証してからpayloadを認証判断へ利用してください。

  6. 署名と全claimsの検証成功後、tokenの署名segment、またはtoken全体のSHA-256 digest をサーバー側replay storeへ原子的なinsert-if-absentで登録する。 初回insertだけを成功とし、重複tokenは拒否する。保存値からtokenを復元できない形式を選び、 TTLはtokenのexp以上(少なくともtoken失効時刻まで記録が残る期間)にする。
  7. replay storeへのsession発行前に消費確定を完了し、初回消費を確認できた場合だけアプリ固有のセッションを発行する。 cookieへ HttpOnly、Secure、適切な SameSite を設定する。
  8. callback処理後はtokenをURLから除去し、保存済みの元の保護ページを一回消費してredirectする。不正な値は安全なdefaultへ戻す。
4

認証フロー

1. 利用者 → 別アプリ /auth/portal → 一回限りstateを発行
2. 別アプリ → Portal /api/auth/broker/start?app=…&returnTo=…
3. Portal → Googleログインと許可ポリシー確認
4. Portal → 別アプリ /auth/portal/callback?token=…
5. 別アプリ → stateを照合・消費 → 署名・claims検証 → ローカルsession発行
6. 別アプリ → tokenのない保護ページへredirect
5

エラー処理と禁止事項

条件契約
未登録app ID未登録app IDは400で拒否
許可リスト外のreturnTo別origin・port・path、credentials、hashを含むcallbackは400で拒否
Portal側の環境変数なしPortal secret未設定は500(値は応答しない)
  • 署名不一致、iss/aud不一致、exp期限切れ、必須claimの欠落・型不正、未許可emailは認証失敗として拒否し、sessionを発行しない。
  • stateの欠落・不一致・期限切れ・再利用を拒否し、ログインCSRFやsession固定を防ぐ。
  • token、共有secret、session、API key、認証回避parameterを画面に表示せず、ログやPortal URLへ保存しない。
  • tokenをlocalStorageなどの永続的なブラウザストレージへ保存しない。
  • Portal cookieを別ドメインのアプリと共有しない。
  • アプリカード、推測しにくいURL、Cloudflareの経路制御だけを認証として扱わない。

利用者向けエラーには、自動転送ではなく明示的な再試行ボタンとサポート導線を持つ画面を出します。 失敗状態でPortalと別アプリを自動往復させる無限redirectを避け、内部の検証理由やsecret情報は表示しません。

6

ローカル検証と本番検証

ローカル検証

  • Portalと別アプリをローカルで起動し、登録済みlocalhost originで認証往復を確認する。
  • 双方でverify、lint、buildを実行する。Portalでは npm run verify:connection-docs、npm run lint、npm run build を使う。
  • ブラウザで直接URL、Portal経由、公開ルート、失敗・再試行経路を確認する。

本番検証

  • 双方の本番環境へsecretを安全に設定し、broker metadataのconfigured状態を確認する。
  • 双方のverify、lint、build、デプロイ結果を確認してから本番URLで認証往復する。
  • ブラウザのデスクトップとモバイルで、表示、redirect、cookie、URLからのtoken除去を確認する。
7

完了条件

  • 直接URLへの未認証アクセスが保護されている
  • Portalログイン後に元のアプリへ戻れる
  • 別アプリ固有のセッションが発行される
  • broker tokenがcallback後のURLに残らない
  • 一回限りのstateがcallbackで照合・消費され、再利用できない
  • replay storeでtokenを一回だけ消費し、同じtokenの再利用を拒否できる
  • 意図した公開ルートは未ログインでも利用できる
  • Portal側と別アプリ側の検証が両方成功する
  • Portalカードとmanifestの登録内容が一致している
  • 本番ブラウザでデスクトップとモバイルの表示・認証を確認済み

機械可読リソース