プログラミング ネットワーク ソフトウェア 公開日 2026.05.20 更新日 2026.05.20

CORS のハマりパターン 10 とデバッグチェックリスト

CORS エラーは Web 開発で最頻出のハマりポイント。「プリフライト失敗」 「Credentials とワイルドカードの衝突」 「プロキシ層での重複ヘッダ」 など、エラーメッセージだけでは原因が掴みにくいパターンが多数あります。実務で踏みやすい 10 パターンと、原因切り分けのデバッグチェックリストを整理します。

先に要点

  • CORS エラーの大半は、プリフライト(OPTIONS)が失敗しているレスポンスヘッダが正しく付いていない のどちらか。ブラウザのエラーメッセージは表面的なので、必ず DevTools のネットワークタブで OPTIONS のレスポンスを確認するのが原因特定の出発点。
  • 典型的なハマりパターンは 10 個に大別できる: Origin の指定ミス」 「Credentials とワイルドカードの衝突」 「プリフライト未対応」 「許可ヘッダ / メソッド漏れ」 「プロキシ層での重複ヘッダ」 「ブラウザのプリフライトキャッシュ」 「Cookie の SameSite 衝突」 「エラーレスポンスにヘッダが付かない」 「Service Worker / CDN の介在」 `フレームワーク標準と手書きの混在
  • デバッグの王道は ① ブラウザの DevTools で失敗したリクエストを特定 → ② OPTIONS のレスポンスヘッダを確認 → ③ 本リクエストのレスポンスヘッダを確認 → ④ サーバ側ログで再現 → ⑤ プロキシ層を一段ずつ確認。「サーバ側だけ」 「フロント側だけ」 を見ても原因が見えないことが多い。
  • CORS を緩めて回避」 は最後の手段。「 Access-Control-Allow-Origin: *」 を本番でつけると認証情報を一切送れない(「Credentials: true」 と両立しない)し、「セキュリティリスク」 も増える。原則は 許可する Origin をホワイトリストで明示
  • 「 自前で CORS ヘッダを書く」 のはほぼ事故のもと。フレームワーク標準のミドルウェア(Laravel の 「HandleCors」、Express の 「cors」 パッケージ、Spring の 「CorsConfiguration」 など) を使い、設定だけで済ませるのが安全。

CORS で詰まった」 ── Web 開発者なら誰もが通る試練です。CORS の仕組み自体は 「異なる Origin 間のリクエストを安全に行うためのブラウザ機構」 とシンプルですが、「どこでヘッダが正しく付いていないか」 を特定するのが難しく、多くの時間を吸い取ります。

ざっくり言うと、CORS エラーは プリフライト(OPTIONS)が失敗 / レスポンスヘッダが不足 / プロキシ層で改変される のいずれかが原因です。エラーメッセージは表面的なので、「どのリクエストの、どのヘッダが、なぜ来ていないか」 を切り分ける手順を持っているかどうかで、解決速度が大きく変わります。

この記事では、実務で踏みやすい 10 個のハマりパターン と、原因特定のための デバッグチェックリスト を整理します。CORS の基礎は CORS とは?初心者向けの解説 を併読してください。

ハマりパターン 10 と直し方

それぞれの失敗パターンと、原因 + 直し方をセットで整理します。

1. Origin の指定ミス

最も基本的で、最も頻発するパターン。

症状

` https://example.com を許可したつもりが、「https://www.example.com からのリクエストが弾かれる」。「末尾スラッシュの有無」 「スキーム(http/https)の違い」 「ポート番号の違い」 で別 Origin 扱い。

対処

許可リストは スキーム + ホスト + ポートを完全一致で指定。動的に複数許可するなら、「許可リストと突き合わせて該当する Origin だけ Access-Control-Allow-Origin に返す」 設計にする。* (ワイルドカード)を本番で使わない

2. Credentials とワイルドカードの衝突

仕様の最重要ポイントの一つで、見落とすと永遠に解決しない。

症状

CookieAuthorization ヘッダを送りたいので fetch に credentials: include」 を付けたら、「Access-Control-Allow-Origin: * と credentials は両立できない」 エラー。

対処

Credentials を送る場合は、Allow-Origin に具体的なドメインを返す。「Vary: Origin」 ヘッダも一緒に付けてキャッシュ事故を防ぐ。「Allow-Credentials: true」 を必ず付ける。「 ワイルドカード + 認証情報」 は構造的に不可能と覚える。

3. プリフライト(OPTIONS)未対応

サーバ側で 「OPTIONS メソッドを受け付ける設定が無い」 ケース。

症状

POST / PUT / DELETE / カスタムヘッダ」 を含むリクエストで OPTIONS が 404 or 405。本リクエストが届く前にブラウザがブロック。

対処

サーバが OPTIONS メソッドに対し 200/204 + 必要な CORS ヘッダを返すフレームワーク標準の CORS ミドルウェアを使えば自動。「API Gateway」 ならコンソールで 「Enable CORS」 を有効化。ALB / nginx」 でも 「OPTIONS をバックエンドへ通す or 自前で応答」 設定が必要。

4. 許可ヘッダ / メソッド漏れ

POST は通るのに PATCH が通らない」 「Content-Type は OK だが Authorization が通らない」 系。

症状

「 Access-Control-Allow-Methods」 や 「Access-Control-Allow-Headers」 に必要な値が含まれていない。`プリフライトの OPTIONS リクエストで、ブラウザが送る 「Access-Control-Request-Headers」 と一致しないと弾かれる」。

対処

サーバ側で 許可するメソッドとヘッダを明示的に設定。「Authorization」 「Content-Type」 「X-Requested-With」 のような追加ヘッダはそれぞれ明示が必要(「* で全許可」 は credentials と両立不可)。

5. プロキシ層での重複ヘッダ

ALB / CloudFront / nginx / Cloudflare」 を経由すると起きる難しいパターン。

症状

バックエンドアプリも CORS ヘッダを返し、プロキシも CORS ヘッダを足す」 ことで、Access-Control-Allow-Origin が2つ になり 「仕様違反でブラウザがブロック」。

対処

CORS は1箇所だけで処理する。アプリ側で処理するなら、プロキシ側では足さない。CloudFront / API Gateway のような前段で処理するなら、アプリ側のミドルウェアは外す。どこで CORS が付与されているかを必ずネットワークタブで確認

6. ブラウザのプリフライトキャッシュ

「 設定を直したのにエラーが消えない」 系の典型。

症状

サーバ側で CORS 設定を修正したのに、ブラウザはまだ ` 古い OPTIONS のレスポンスをキャッシュしている。「Access-Control-Max-Age」 で指定した秒数の間、プリフライトを再送しない。

対処

` DevTools の Network タブで 「Disable cache」 にチェック」、または シークレットウィンドウで確認。修正中は 「Access-Control-Max-Age を 60 秒など短く」 設定しておくとデバッグが楽。本番では長め(600〜86400 秒)に。

Cookie が送信されない」 系で、近年 SameSite デフォルト変更で増えた。

症状

「 クロスサイトの fetch で Cookie が送られない」。「SameSite=Lax がデフォルト」 なので、クロスサイトの XHR / fetch では Cookie が送信されない。

対処

クロスサイトで Cookie を送るなら SameSite=None; Secure を Set-Cookie に設定。さらに HTTPS 必須。「第三者 Cookie ブロック」 が有効なブラウザでは Secure でも届かない可能性があるので、同一 Origin に寄せる」 か `バックエンド側でセッションを管理 する設計が長期的には安全。

8. エラーレスポンスに CORS ヘッダが付かない

「 200 はうまく行くのに 500 だけ CORS エラー」 という不思議な状況。

症状

「 エラー時(500、404)に CORS ミドルウェアを通らない実装」 になっていて、エラーレスポンスに 「Access-Control-Allow-Origin」 が付かない。ブラウザは CORS エラーとして処理し、実際のエラー詳細が見えない

対処

CORS ミドルウェアを エラーハンドラより前段に置くフレームワークによっては 「エラーハンドラ後でも CORS を付ける設定」 が必要。「必ず正常系とエラー系の両方でテスト」 する。

9. Service Worker / CDN の介在

「 ローカルでは通るのに本番だけエラー」 系。

症状

Service Worker」 がリクエストをインターセプトして CORS ヘッダを落とす、「CloudFront / Cloudflare」 がレスポンスヘッダをキャッシュしたりキャッシュキーで Vary を考慮していない、で起きる。

対処

Service Workerfetch ハンドラ内で Response の cors モードを明示CloudFrontキャッシュポリシーに Origin ヘッダを含める 設定。CloudFront の入門 で扱った 「キャッシュキー設計」 と同じ話。

10. フレームワーク標準と手書きの混在

「動かないので CORS ヘッダを自分で書き足した」 が事故るパターン。

症状

` Laravel の HandleCors が動いている上に、ルート内で手書きの header(`Access-Control-...「) を追加」 で、ヘッダが重複したり矛盾する値になる。

対処

フレームワーク標準のミドルウェアに統一する。設定ファイル(Laravel なら 「config/cors.php」、Express なら 「cors」 パッケージのオプション)で許可 Origin / メソッド / ヘッダを管理。「手書きの CORS ヘッダは削除」。

デバッグチェックリスト

エラーに遭遇したら、この順序で確認すると原因に最短で到達できます。

読み込み中...

「どこで失敗しているか」 を切り分けると、「修正する場所」 が一意に決まります。

CORS を扱う時の設計原則

「目の前のエラーを直す」 だけでなく、「今後ハマらない設計」 を作るための原則を整理します。

CORS は 1 箇所だけで処理

「 プロキシ層・アプリ層・複数ミドルウェア」 のうち 「 どこか 1 箇所」 に統一。「複数で処理するとヘッダ重複」 が起きる。「API Gateway を最前段にして CORS を処理 → バックエンドは CORS を意識しない」 が現代的。

同一 Origin に寄せられるなら寄せる

フロントAPI が別ドメイン」 だと CORS の悩みが永遠に続く。フロントAPI を同一ドメインに統一(リバースプロキシで 「/api/*」 を裏のバックエンドに振る、BFF パターン)。CORS の悩みが構造的に消える。

許可 Origin はホワイトリスト

「 *」 で全許可は本番では絶対に NG。環境別の許可リストを設定で管理(本番 / ステージング / ローカルの 3 つのみ)。動的に複数許可するなら、Origin を検証してから動的に Allow-Origin を返す。

フレームワーク標準を信じる

Laravel の HandleCors」 「Express の cors」 「Spring の CorsConfiguration」 「FastAPI の CORSMiddleware」 のような標準を使い、設定だけで完結させる。「手書きヘッダ追加」 は事故のもと。

CORS に関するよくある質問

Q. CORS エラーはサーバ側?フロント側?

A. ほぼ常にサーバ側の設定問題です。CORS はブラウザが安全のためにブロックする仕組みで、「サーバが正しいヘッダを返せばブラウザが許可する」 という流れ。フロント側の修正で消えるのは Origin / Credentials / mode の設定ミスのときくらいで、本質的にはサーバ側を直すことになります。

Q. プリフライト(OPTIONS)はいつ送られますか?

A. 「 シンプルリクエスト」 以外の場合に送られる。シンプルリクエストは 「GET / HEAD / POST(application/x-www-form-urlencoded、multipart/form-data、text/plain のみ)」 で、「カスタムヘッダなし」 が条件。「JSON で POST」 「Authorization ヘッダ付き」 「PUT / DELETE / PATCH」 はプリフライトが発生します。

Q. 「Access-Control-Allow-Origin: *」 を本番で使ってはダメ?

A. 公開 API(認証不要)なら OK、認証が絡むなら NG。「Cookie や Authorization ヘッダ」 を送る場合、ワイルドカードと credentials は両立できないので、「具体的なドメインを返す」 必要があります。「公開データを返す API」(オープンデータ、CDN 配信用 API)では 「*」 が適切な場合もあります。

Q. CloudFront / API Gateway を前段に置いたら CORS はそこで処理?

A. 推奨される現代的な構成ですCloudFrontAPI Gateway の機能で CORS ヘッダを付与し、バックエンドアプリは CORS を意識しない設計にすると、「バックエンドの差し替えやリプレース」 が楽になります。「CORS は前段の責務」 と決めておくのが運用上シンプル。

Q. Service Worker で CORS が変わる?

A. 変わります(意図的に設定すれば)Service Worker の fetch ハンドラで 「Response の cors モード」 や 「cache のレスポンス」 を返すと、ブラウザの CORS 判定が変わります。「オフライン対応の SW がレスポンスを操作」 で、「CORS ヘッダが落ちる」 事故が起きやすい。SW 経由の fetch も忘れず確認。

Q. ローカル開発で CORS を回避するベストプラクティスは?

A. フロントの dev サーバで API へのプロキシを設定するのが定番。「Vite / Next.js / CRA」 のいずれも 「proxy 設定」 で 「/api/* を localhost:3001 に転送」 のような設定が可能。同一 Origin として扱われるので CORS が不要になります。「本番でも同一 Origin に寄せる」 ことを目指せば、CORS の悩みは構造的に消えます。

Q. CORS と CSRF はどう違いますか?

A. 守る対象が違う。CORS は 「ブラウザがクロスサイトリクエストを安全にする仕組み」、CSRF は 「攻撃者が他人を装って認証済みリクエストを送らせる攻撃」。「CORS が正しく動いているから CSRF も守られる」 は 必ずしも成立しないCSRF 対策は別途 CSRF トークン or SameSite Cookie で行う必要があります。

まとめ

CORS エラーは プリフライトが失敗 / レスポンスヘッダ不足 / プロキシ層の改変 のいずれかが原因です。`エラーメッセージは表面的なので、「DevTools の Network タブで OPTIONS の応答を確認する」 のが原因特定の出発点。

「目の前のエラーを直す」 だけでなく、1 箇所だけで CORS を処理 / 同一 Origin に寄せる / ホワイトリスト / フレームワーク標準を使う という設計原則を守れば、CORS の問題は構造的に減ります。「手書きヘッダで何とかする」 のは事故のもとなので、フレームワークの標準ミドルウェアに統一するのが現代の正解です。

参考リンク

あとで見返すならここで保存

読み終わったあとに残しておきたい記事は、お気に入りからまとめて辿れます。