メインコンテンツへスキップ
ブログ一覧に戻る
Web制作・運用

CORSは「攻撃を防ぐ壁」ではない|共有を許可する仕組みだと分かるとエラーの読み方が変わる

2026年9月18日
8分で読めます
CORSは「攻撃を防ぐ壁」ではない|共有を許可する仕組みだと分かるとエラーの読み方が変わる

この記事の結論

CORSエラーを「サーバーが弾いている」と考え、Access-Control-Allow-Origin: *を足して済ませていませんか。 WHATWG Fetch StandardとMDNの定義に基づき、CORSが「攻撃者からサーバーを守る壁」ではなく「他人のデータをスクリプトに渡してよいかの宣言」であることを整理します。

CORSは「攻撃を防ぐ壁」ではない|共有を許可する仕組みだと分かるとエラーの読み方が変わる

外部のAPI(他のシステムとデータをやり取りするための窓口)をフロントエンドから呼び出したときに真っ先に遭遇するのが、CORS(Cross-Origin Resource Sharing)エラーです。多くの実装者は、このエラーを見て「サーバーがリクエストを拒否している」と考えます。そして解決策としてAccess-Control-Allow-Origin: をサーバー側に追加し、エラーが消えたら「直った」と判断します。

この理解には見落としがあります。CORSは、攻撃者をサーバーの手前で止める壁ではありません。ブラウザが預かっている「他人のデータ」を、いま動いているスクリプトに渡してよいかどうかの宣言です。この前提を取り違えると、エラーは消えても、本来サーバー側にあるべき認可の仕組みが空いたままになっている、という事態が起こり得ます。

30秒で要点

  • CORSエラーは「サーバーに届かなかった」ではなく「ブラウザがレスポンスをスクリプトに渡すのを拒否した」ときに起きる(届く/読めるは別問題)
  • フォーム送信に見える「単純リクエスト」は、サーバーが何も設定しなくても届く。CSRFの脅威はフォーム送信と変わらないため、サーバー側は元々CSRF対策をしている前提になっている
  • CORS設定は、サーバー側の認可・CSRF対策の代わりにはならない
  • CORSエラーの詳細は、セキュリティ上の理由でJavaScriptからは取得できない仕様になっている

なぜ「サーバーが弾いている」と誤読してしまうのか

CORSエラーが誤解されやすいのには、仕様上の理由があります。MDNの解説にある通り、CORSの失敗はコンソールにエラーとして表示されますが、セキュリティ上の理由からその詳細情報はJavaScriptから取得できません。エラーオブジェクトをtry/catchで受け取っても、原因を示す情報はほとんど手に入らない仕様です。

情報が得られないまま目の前のエラーだけを見ると、人は「何かがブロックされた」という結果から「サーバー側で拒否された」という原因を推測しがちです。さらに、Access-Control-Allow-Originという名前自体が「アクセスを制御する」という響きを持つため、「アクセス権限の設定」=「サーバーへの入場管理」だと連想しやすくなります。この名前からの連想と、詳細情報が見えない仕様が重なって、「サーバーが弾いている」という誤読が生まれます。

確かなこと・まだ確かではないこと

確かなこと:WHATWG Fetch Standard(CORSを定義する規範文書)は、CORSプロトコルについて「HTMLのform要素より柔軟な取得を可能にしながら、レスポンスをクロスオリジンで共有できるようにするため」の仕組みだと説明しています。そして、これがopt-in(明示的な許可)でなければならない理由として、ファイアウォールの内側(イントラネット)にあるレスポンスのデータが漏れること、および資格情報を含むリクエストで機微なデータが漏れることの2点を挙げています。

MDNの「単純リクエスト」の節も明確です。フォーム送信に見えるリクエストについて、「サーバーは(プリフライトリクエストに応答する形で)opt-inしなくても、そのリクエストを受け取ってしまう。CSRFの脅威はフォーム送信のときと変わらないため」だとしています。つまり、単純リクエストはCORS設定の有無にかかわらずサーバーに届きます。ただし、そのレスポンスをスクリプトと共有するには、Access-Control-Allow-Originによるopt-inが別途必要です。

まだ確かではないこと:個々のブラウザが、プリフライト後のリダイレクト追従などの細部でどこまで仕様通りに実装しているかは、仕様書だけでは断定できません。挙動を厳密に確認したい場合は、実際に使用しているブラウザの最新の実装状況を個別に確認する必要があります。

CORSが守っているのは「サーバー」ではなく「ブラウザの利用者」

ここまでの2つの一次情報を合わせると、CORSの立ち位置が見えてきます。

CORSは「サーバーへの到達を防ぐ」仕組みではありません。単純リクエストはサーバー側が何も言わなくても届きます。CORSが制御しているのは、その後の一点だけです。「届いたレスポンスの中身を、いま動いているスクリプト(=そのオリジンのWebページ)に渡してよいか」という点です。

この違いは、CORSが誰の利益を守っているかを考えると分かりやすくなります。CORSが無ければ、あなたがログイン中の別サイトのデータを、悪意あるWebページ上のスクリプトが(ブラウザに保存された認証情報を使って)勝手に読み取れてしまう可能性があります。CORSはこれを防ぐために、「このオリジインのスクリプトにレスポンスを渡してよいか」をサーバー側に宣言させる仕組みです。守られているのは、サーバー自身の安全というより、そのブラウザを使っている利用者のデータです。

したがって、CORS設定は「サーバーへの不正アクセスを防ぐ」認可の仕組みの代わりにはなりません。単純リクエストはCORS設定が無くてもサーバーに届く以上、「誰がこのAPIを呼んでよいか」「呼んだ人が本当に本人か」を確認する認可・認証と、フォーム送信と同種の脅威に対するCSRF対策は、CORSとは別にサーバー側で用意しておく必要があります。Access-Control-Allow-Origin: を設定してエラーが消えたときに確認すべきは、「エラーが消えたこと」ではなく、「そのエンドポイントに認可チェックが実在するか」です。

判断軸:CORSエラーに遭遇したら確認する3つの問い

CORSエラーに当たったとき、原因を切り分けるには次の順番で確認すると、誤ったopt-inの拡大を避けやすくなります。

  1. そのリクエストは本当にサーバーに届いているか:ブラウザの開発者ツールのネットワークタブで、リクエストが送信されレスポンスが返っているかを確認します。届いていれば、問題は「読めるかどうか」の側にあります。
  2. レスポンスヘッダにAccess-Control-Allow-Originが含まれているか:含まれていなければ、サーバー側がこのオリジンへの共有を許可していません。ここでいきなり*を追加する前に、次の問いに進みます。
  3. このエンドポイントには、CORS設定とは別に認可(誰が呼んでよいか)が存在するか:存在しない、あるいは曖昧な場合は、CORSの許可範囲を広げる前に、サーバー側の認可を先に設計する必要があります。CORSを緩めることは、認可の不在を隠すことにしかなりません。

自分でも試せる、最小の検証

自社のプロダクトで、CORSの許可設定(Access-Control-Allow-Origin)を通しているAPIエンドポイントを1つ選んでください。そのエンドポイントの実装を開き、「このリクエストを送ってきたのが誰か」をサーバー側のコードが実際に確認しているかどうかを見てみてください。CORS設定だけがあり、サーバー側の認可チェックが見当たらない場合、それはCORSの許可範囲の広さとは無関係に、別の対応が必要な箇所です。

判断の土台として押さえておくこと

  • CORSは「サーバーへの到達を防ぐ壁」ではなく、「届いたレスポンスをスクリプトに渡してよいかの宣言」
  • 単純リクエストはCORS設定の有無にかかわらずサーバーに届く。サーバー側のCSRF対策は元々必要という前提になっている
  • CORS設定は、サーバー側の認可・CSRF対策の代替にはならない
  • CORSエラーの詳細はJavaScriptから取得できない仕様のため、原因の切り分けは開発者ツールのネットワークタブで行う

Webアプリケーションのセキュリティ設計を含め、実装の前提を洗い出して整理したい場合は、状況を整理するところから始めることができます。

状況を整理する(15分)

より深く学ぶ

参考資料・引用元

  • WHATWG, "Fetch Standard — HTTP CORS protocol". https://fetch.spec.whatwg.org/#http-cors-protocol
  • MDN Web Docs, "Cross-Origin Resource Sharing (CORS)". https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS

よくある質問(FAQ)