TL

CORS の内部動作とプリフライト判定

別オリジン API がブラウザで弾かれる理由が腑に落ち、単純/プリフライトの分岐と Access-Control 系ヘッダの評価順、credentials とワイルドカードの制約まで設定根拠で語れるようになる。

応用セキュリティCORS同一オリジンポリシーブラウザWebアプリ最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.CORS はサーバーが返す Access-Control 系レスポンスヘッダで「別オリジンからの読み取りを許す」と宣言する仕組み。ブロックするのはサーバーではなくブラウザ。
  • 2.リクエストは単純(safelisted)かプリフライト要かに分岐し、後者は本番前に OPTIONS で許可を問い合わせる。判定基準はメソッド・ヘッダ・Content-Type。
  • 3.credentials 付き要求では Access-Control-Allow-Origin にワイルドカード * を使えず、Origin の反射には Vary: Origin と厳密な許可リストが必須。

何を守る仕組みなのか

CORS(Cross-Origin Resource Sharing)は、ブラウザの根幹である同一オリジンポリシー(SOP)を、サーバーの許可宣言に基づいて部分的に緩和するための規格です。オリジンとは スキーム + ホスト + ポート の三つ組で、https://app.example:443https://api.example:443 はホストが違うので別オリジンです。

ここで最初に押さえるべき本質は、CORS はリクエストの送信そのものを止めないということです。クロスオリジンの fetch でも、リクエストは多くの場合サーバーに到達し、サーバーは処理してレスポンスを返します。ブラウザは返ってきたレスポンスを見て、「このオリジンに読ませてよい」とサーバーが明示していなければ、レスポンスを JS から読めないように遮断するだけです。つまり保護対象は「攻撃者サイトが、被害者の権限で別オリジンのレスポンス本文を盗み読むこと」であって、書き込み(副作用)を止めるのは CSRF 対策の領分です。両者を混同すると設計を誤ります。

CORS は CSRF 対策ではない

「CORS を入れたから CSRF は防げる」は誤りです。単純リクエスト(後述)はプリフライトなしでサーバーに届き、副作用は CORS の判定前に起きてしまうことがあります。読み取りを遮断しても、送金や設定変更といった書き込み自体は実行され得ます。状態変更には CSRF トークンや SameSite Cookie を別途用意してください。

単純リクエストとプリフライトの分岐

ブラウザはクロスオリジン要求を、**プリフライト不要の「単純リクエスト(safelisted request)」**か、事前確認が要るリクエストかに振り分けます。すべての条件を満たすものだけが単純リクエストです。

判定軸単純(プリフライト不要)の条件外れるとどうなるか
メソッドGET / HEAD / POST のいずれかPUT・DELETE・PATCH 等はプリフライト必須
Content-Typetext/plain・application/x-www-form-urlencoded・multipart/form-data のみapplication/json はプリフライト必須
リクエストヘッダsafelisted(Accept・Accept-Language・Content-Language 等)のみAuthorization や独自ヘッダを足すとプリフライト必須
その他ReadableStream を本文に使わない・XHR の upload にイベントを付けないストリーム送信等もプリフライト対象

実務で最もよく踏むのが Content-Type です。fetch で JSON を送る Content-Type: application/json は safelisted に含まれないため、それだけでプリフライトが発生します。逆に言えば、HTML の <form>application/x-www-form-urlencoded で POST できてしまうのは単純リクエストだからで、これが CORS だけでは CSRF を防げない理由でもあります。

プリフライトの往復を読み解く

プリフライトは、本番リクエストの前にブラウザが自動で投げる OPTIONS の問い合わせです。「これからこのメソッドとこのヘッダで送りたいが、許すか」をサーバーに尋ねます。

OPTIONS /orders HTTP/1.1
Host: api.example
Origin: https://app.example
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: authorization, content-type

サーバーがこれを許可するなら、対応するヘッダを返します。

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example
Access-Control-Allow-Methods: GET, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 600

ブラウザの評価アルゴリズムは厳密です。Access-Control-Request-Method の値が Allow-Methods の集合(例 {GET, PUT, DELETE})に含まれ、かつ Access-Control-Request-Headers に挙げた各ヘッダがすべて Allow-Headers に含まれて初めて通過します。一つでも欠ければプリフライトは失敗し、本番リクエストは送られませんAccess-Control-Max-Age はこのプリフライト結果をブラウザがキャッシュしてよい秒数で、同条件の連続呼び出しで OPTIONS の往復を省けます(上限はブラウザ依存で、Chromium 系は約 2 時間)。

Allow-Headers にワイルドカードを使うときの落とし穴

Access-Control-Allow-Headers: *ワイルドカードとして機能しますが、Authorization だけは別扱いで、* ではカバーされません。Authorization を許可したいなら、Access-Control-Allow-Headers: *, Authorization のように明示する必要があります。credentials 付き要求では後述のとおり * 自体が無効になる点も合わせて注意してください。

credentials 付き要求とワイルドカードの制約

Cookie やクライアント証明書、Authorization ヘッダを伴う「資格情報付き要求」では、CORS の制約が一段厳しくなります。ブラウザは fetch(..., { credentials: 'include' }) のときだけ Cookie を送り、レスポンス側に Access-Control-Allow-Credentials: true が無ければレスポンスを遮断します。

決定的な制約は、credentials 付き要求では Access-Control-Allow-Origin: * が無効になることです。同様に Allow-Headers: *Allow-Methods: * のワイルドカードも、credentials 付きではリテラルの * という文字としてしか解釈されません。許可するなら具体的なオリジン文字列をそのまま返すしかありません。

レスポンスヘッダcredentials なしcredentials あり(include)
Access-Control-Allow-Origin* または具体オリジン具体オリジンのみ(* は不可)
Access-Control-Allow-Credentials不要true が必須
Allow-Headers / Allow-Methods の *ワイルドカードとして有効リテラル * 扱い(実質無効)
Vary: Origin* なら任意 / 反射なら必須実質必須(オリジンごとに応答が変わるため)

* が使えないため、複数オリジンを許可したいサーバーは「リクエストの Origin を読み、許可リストに含まれていればその値をそのまま Access-Control-Allow-Origin に反射する」という実装を取ります。ここで重要なのが Vary: Origin です。レスポンスがオリジンによって変わるのに Vary を付けないと、CDN や共有キャッシュがあるオリジン向けの Allow-Origin を別オリジンに配ってしまい、キャッシュ汚染で許可が漏れます。

誤設定による情報漏洩

CORS の事故の大半は、緩和を「広げすぎる」ことで起きます。レスポンスを読めるのは攻撃者のスクリプトであり、被害者の Cookie で認証された状態なら、その応答(個人情報・トークン等)がそのまま盗まれます。

Origin の無検証な反射 + credentials は最悪の組み合わせ

よくある危険コードは、Origin ヘッダを検証せずそのまま反射し、同時に credentials を許す実装です。

// 危険:どんなオリジンでも許可してしまう
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');

これは「任意のサイトが、ログイン中の利用者の権限で API を読める」ことを意味します。* を避けたつもりが、反射によって実質「全許可かつ credentials 付き」という、ワイルドカードより危険な状態を作っています。

誤設定のパターンと対策を整理します。

アンチパターンなぜ危険か正しい対処
Origin を検証せず反射全オリジンに credentials 付きで読み取りを許す完全一致の許可リストで照合してから反射
許可判定が前方一致/部分一致evil-app.example や app.example.attacker.com が通るホスト全体を厳密一致で比較
null オリジンを許可sandbox iframe や file:// から Origin: null を悪用されるnull を許可しない
* と credentials の併用を反射で回避実質全許可と同義認証付き API は許可元を最小化

許可判定では部分一致を絶対に使わないことが鉄則です。origin.endsWith('app.example') のような実装は app.example.attacker.com を、origin.startsWith('https://app.example')https://app.example.evil.com を通してしまいます。ホスト名を完全一致で照合してください。

const allowed = new Set(['https://app.example', 'https://admin.example']);
const origin = req.headers.origin;
if (origin && allowed.has(origin)) {       // 完全一致のみ
  res.setHeader('Access-Control-Allow-Origin', origin);
  res.setHeader('Access-Control-Allow-Credentials', 'true');
  res.setHeader('Vary', 'Origin');         // キャッシュ汚染の防止
}
試験・面接で問われる要点
  • CORS が遮断するのはレスポンスの読み取りであり、リクエスト送信や副作用ではない(書き込み防止は CSRF 対策)。
  • プリフライトを誘発する三条件は「非単純メソッド」「非 safelisted な Content-Type(例 application/json)」「safelisted 外のヘッダ(例 Authorization)」。
  • Access-Control-Allow-Origin: * は credentials 付き要求では使えず、具体オリジンの反射には Vary: Origin が必須。

全体像の中での位置づけ

CORS は「ブラウザがレスポンスを読ませてよいか」を制御する一機構にすぎません。サーバーへの到達やヘッダ全般の安全設定は セキュリティヘッダ の領域であり、レスポンスを読まれた場合の被害は XSS と地続きです。一方、サーバー側が外部に対して内部リソースへリクエストを飛ばしてしまう SSRF は CORS とは別レイヤの問題で、CORS では防げません。CORS は読み取り許可の宣言、CSRF はトークンによる書き込み保護——役割を分けて、両者を重ねて設計するのが正解です。

セキュリティ Article

CORS の内部動作とプリフライト判定を実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

セキュリティ

比較で見る軸

難易度: advanced / カテゴリ: セキュリティ / タグ数: 5

導入後に効く点

リクエストは単純(safelisted)かプリフライト要かに分岐し、後者は本番前に OPTIONS で許可を問い合わせる。判定基準はメソッド・ヘッダ・Content-Type。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
セキュリティ
タグ数
5

判断チェックリスト

  • 自社の用途が「セキュリティ / CORS」に近いか確認する。
  • 強みである「CORS はサーバーが返す Access-Control 系レスポンスヘッダで「別オリジンからの読み取りを許す」と宣言する仕組み。ブロックするのはサーバーではなくブラウザ。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

セキュリティCORS同一オリジンポリシーブラウザWebアプリセキュリティCORS同一オリジンポリシー