なぜ危ないのか
OAuthのcallback URLがHTMLにハードコードされている場合、攻撃者はそのURLを使って偽のOAuth認可フローを開始できる。ユーザーに「このサービスへのアクセスを許可しますか?」という正規の認可画面を見せながら、callback先を攻撃者の管理下のエンドポイントに誘導するフローを構築される危険がある。
AIがデモ用に生成するOAuth実装は、callback URLを`http://localhost:3000/callback`や`https://example.com/auth/callback`のような固定値でハードコードすることが多い。これを本番環境に流用すると、OAuth Providerの設定と食い違いが生じてエラーになるだけでなく、callback URLが本番ドメインを指していた場合はセキュリティリスクが生まれる。
`client_secret`がHTMLに埋め込まれているケースは特に深刻だ。AIは動作するデモを優先するためにclient_secretをフロントエンドのJSに書いてしまうことがあるが、ブラウザの開発者ツールを開けば誰でも見られる状態になる。この情報が漏れると、攻撃者が同じclient_idとclient_secretを使って正規のOAuthフローを完全に模倣できる。
- 相手がログインなしで開ける状態か確認する
- PCとスマホで最低1回ずつ表示を確認する
- 内部情報・個人情報・不要な外部送信が残っていないか見る
- レビュー期限と修正時の差し替え方を決めておく
ソースで見る場所
HTMLとJS内で`callback`・`redirect_uri`・`client_id`・`client_secret`という文字列を検索する。`grep -in 'callback\|redirect_uri\|client_id\|client_secret' output.html`を実行し、ヒットした行にURLや英数字の長い文字列が含まれている場合は内容を精査する。特に`client_secret`の存在は即座に削除が必要だ。
OAuth認証フローを開始するボタンや関数の実装を探す。`window.location.href = 'https://accounts.google.com/o/oauth2/auth?'`のようにOAuthの認可エンドポイントにリダイレクトするコードがある場合、そのURLパラメータに含まれる`client_id`と`redirect_uri`の値を確認する。
fetch・XMLHttpRequestを使ってOAuthトークンを取得するコードがあれば、そのエンドポイントURLとリクエストボディを確認する。`grant_type=authorization_code`と一緒に`client_secret`を送信しているコードはフロントエンドとして実装すべきでないパターンであり、サーバー側に移す必要がある。
- HTML内の外部script・form action・iframeを確認する
- APIキーやトークンらしき文字列がないか検索する
- 画像・CSS・JSのパスが公開後も解決できるか見る
- noindexと認証を混同せず、必要なら両方設定する
安全に共有する設定
OAuth関連の認証情報を除去したデモ用HTMLを共有する場合は、`client_id`をプレースホルダー(例:`YOUR_CLIENT_ID`)に置き換え、`client_secret`はHTMLから完全に削除した状態で共有する。受け取り側がこのHTMLを動かすには別途設定が必要であることを共有メモに明記しておく。
OAuthのフローを視覚的に確認させたい場合は、実際の認証を行わないモックUI(ボタンがあるがクリックしても認証フローが起動しない状態)として共有する方法が安全だ。AIにプロンプトで「OAuth認証ボタンはUIとして表示するが、実際のOAuthフローは実装しないこと」と指定すると、このパターンで生成してくれることが多い。
どうしても動作するOAuthフローを見せる必要がある場合は、テスト用のOAuthアプリを専用で作成し、callback URLをローカル環境またはテスト用ドメインのみに限定する。本番用のclient_idは絶対に使わず、共有が終わったらそのテスト用アプリをOAuth Providerのコンソールから削除する。
- HTML内の外部script・form action・iframeを確認する
- APIキーやトークンらしき文字列がないか検索する
- 画像・CSS・JSのパスが公開後も解決できるか見る
- noindexと認証を混同せず、必要なら両方設定する
再発防止ルール
AIにOAuth関連のUIを生成させるプロンプトには「client_secret・アクセストークン・リフレッシュトークンをフロントエンドのJSに含めないこと。認証フローのバックエンド部分はプレースホルダーとして表示するだけにすること」という制約を毎回追加する。
`.gitignore`にHTMLファイルを含めてコミットしている場合でも、`git diff --cached`でステージ状態のファイルを確認し、`client_secret`や実際のclient_idが含まれていないかを確認するstep-hook(pre-commitフック)を設定する。`grep 'client_secret' $(git diff --cached --name-only)`で実装できる。
OAuthを含むHTMLの共有が終わったら、使用したclient_idをOAuth Providerのコンソールでリセットするか、そのアプリを削除する。共有URLの失効と同時にclient_idの廃棄もセットで行う手順を社内手順書に記録しておくと、担当者が変わっても漏れが防げる。
- HTML内の外部script・form action・iframeを確認する
- APIキーやトークンらしき文字列がないか検索する
- 画像・CSS・JSのパスが公開後も解決できるか見る
- noindexと認証を混同せず、必要なら両方設定する
よくある質問
AIが生成したHTMLにclient_idしか含まれていない場合(client_secretはない)、そのまま公開しても安全ですか?
client_idは通常公開情報として扱われるが、攻撃者がそれを使ってフィッシング用のOAuth認可ページを作れるため、公開するHTMLで使うclient_idのredirect_uri設定を最小限に絞ることが必要だ。
OAuthのcallback URLをHTMLに書く代わりに環境変数で渡す方法はありますか?
純粋な静的HTMLでは環境変数を直接参照できないため、ビルドツール(ViteやParcel)を使ってビルド時に環境変数を埋め込む方法か、サーバー側でHTMLを動的に生成してcallback URLを注入する設計が必要になる。
OAuth Providerのコンソールでredirect_uriに登録していないURLへのリダイレクトを攻撃者が試みた場合、どうなりますか?
主要なOAuth Provider(Google、GitHub等)は、認可リクエストのredirect_uriが登録済みURLと完全一致しない場合、認可を拒否してエラーを返す。ただしこれはProviderの保護であり、自社コードのオープンリダイレクトは別途対策が必要だ。