セキュリティ●●●○○

CORS

異なるドメイン間の通信をブラウザが制限・許可する仕組み。フロント開発の頻出壁。

概要

CORS(Cross-Origin Resource Sharing、オリジン間リソース共有)は、ブラウザが課している「異なるオリジンへの JavaScript からの通信制限」を、サーバ側の宣言によって選択的に緩めるための仕組みです。オリジンとは「スキーム + ドメイン + ポート番号」の組のことで、https://app.example.comhttps://api.example.com は別オリジンです。

フロントエンドを localhost:3000 で、API を別ポートや別ドメインで動かした瞬間に、コンソールに赤い「CORS エラー」が出る — Web 開発者なら誰もが一度はぶつかる壁の正体がこれです。エラーはブラウザが出しているものであり、サーバは正常に応答していることも多いため、仕組みを知らないと「どこが悪いのか」の切り分けに延々と迷うことになります。

なぜ生まれたか

前提にあるのは、ブラウザの大原則「同一オリジンポリシー」です。もしこの制限がなければ、罠サイトの JavaScript があなたのブラウザから銀行サイトの API を呼び、Cookie 付きのリクエストでログイン済みの応答を読み取れてしまいます。ブラウザは複数サイトの認証情報を同時に抱えているため、「あるオリジンのスクリプトは、別のオリジンのレスポンスを読めない」という壁が、Web のセキュリティモデルの土台になっています。

ところが Web アプリの構成が進化し、SPA のフロントエンドと REST API のバックエンドを別オリジンで運用する形が当たり前になると、この壁が正当な通信まで妨げるようになりました。かつては JSONP のような「script タグは制限を受けない」ことを悪用気味に使う回避策が横行しましたが、安全とは言い難いものでした。そこで「リソースを提供するサーバ自身が、どのオリジンからのアクセスを許すかを HTTP ヘッダで宣言する」という正式な緩和手段として CORS が標準化されたのです。

詳細

基本の動き: レスポンスヘッダによる許可宣言

仕組みの中心は Access-Control-Allow-Origin レスポンスヘッダです。ブラウザは別オリジンへのリクエストに Origin: https://app.example.com というヘッダを自動で付け、サーバのレスポンスに Access-Control-Allow-Origin: https://app.example.com(または *)が含まれていれば JavaScript にレスポンスを渡し、なければ読み取りをブロックします。

ここで押さえるべき重要な点が2つあります。第一に、判定して遮断しているのはあくまでブラウザです。サーバ間の通信や curl には CORS は一切関係ありません。第二に、多くの場合リクエスト自体はサーバに届いて処理されており、ブロックされるのは「レスポンスの読み取り」です。つまり CORS はサーバを守る仕組みではなく、ブラウザ利用者の認証情報が悪用されて情報が漏れることを防ぐ仕組みだと理解するのが正確です。

app.example.com のページfetch で api.example.com へリクエスト(Origin ヘッダ付き)API サーバAccess-Control-Allow-Origin を含むレスポンスブラウザヘッダを検査し、許可されていれば JS に結果を渡す
CORS の基本: サーバの許可宣言をブラウザが検査する

プリフライトリクエスト

単純な GET やフォーム相当の POST を超えるリクエスト — たとえば Content-Type: application/json の POST、PUT や DELETE、独自ヘッダ付きのリクエスト — では、ブラウザは本番の前に OPTIONS メソッドで「予備審査」を送ります。これがプリフライトリクエストです。「これから POST を、このヘッダ付きで送りたいが良いか」と問い合わせ、サーバが Access-Control-Allow-MethodsAccess-Control-Allow-Headers で承認して初めて本番のリクエストが飛びます。

予備審査と本番、二往復のやり取りになるのがポイントです。

APIサーバブラウザページの JavaScriptAPIサーバブラウザページの JavaScriptfetch で POST を要求OPTIONS で予備審査を送信Allow-Methods と Allow-Headers で承認本番の POST リクエストを送信通常のレスポンスAllow-Origin を検査して結果を渡す

古い形式のフォーム送信で可能だった範囲は予備審査なしで通す(後方互換)、それを超えるものは事前確認する、という線引きです。「API を呼ぶと謎の OPTIONS リクエストが飛んでいる」「OPTIONS に 404 を返していて動かない」というのは、プリフライトを知らないときの典型的なハマりどころです。

同一オリジン判定・単純リクエストかどうか・サーバの許可宣言という3段階の判定は、条件を変えながらシミュレータで試すのが早道です。

⚡ 体験: CORS 判定シミュレータページのオリジン: https://app.example.com
  1. ① オリジン判定
  2. ② 単純 / プリフライト
  3. ③ ブラウザの結論

判定して遮断しているのはサーバではなくブラウザです。単純リクエストでは「届くが読めない」、 プリフライトが必要なリクエストでは「そもそも本番が送られない」— ブロックのされ方が違う点にも注目してください。

認証情報付きリクエストと設定の落とし穴

Cookie や Authorization ヘッダを伴うクロスオリジンリクエストには、さらに厳しいルールがあります。クライアント側で credentials: "include" を指定し、サーバ側は Access-Control-Allow-Credentials: true を返し、かつ Allow-Origin にワイルドカード * は使えず具体的なオリジンを指定しなければなりません。「* にしたのに Cookie が送られない」というのも頻出のつまずきです。

危険なアンチパターンは、リクエストの Origin ヘッダの値をそのまま Allow-Origin にオウム返しし、かつ Allow-Credentials: true にしてしまう設定です。これは事実上「全世界のどのサイトからでも、利用者のセッション権限で API を読んでよい」と宣言するのと同じで、同一オリジンポリシーの保護を自ら破壊します。許可オリジンは明示的なリストで管理するのが原則です。

実務での位置づけ

開発環境では、フロントエンドの開発サーバにプロキシ設定を入れて同一オリジンに見せる方法と、API 側で開発用オリジンを許可する方法がよく使われます。本番ではリバースプロキシで同一オリジンに集約してしまい CORS 自体を不要にする構成も定番です。最後にもう一つ大事な注意として、CORS は CSRF 対策ではありません。CORS が守るのは「レスポンスの読み取り」であって、リクエストが届いて処理されること自体は止めないため、書き込み系の保護には CSRF トークンや SameSite Cookie といった別の対策が必要です。