CORSエラー(Access-Control-Allow-Origin が無い)
「has been blocked by CORS policy」で API 呼び出しが失敗する原因を、プリフライトや credentials の罠まで整理。なぜフロントではなくサーバー側で直すのかまで腹落ちして即解決できる。
- CORS はブラウザが実施するクロスオリジン読み取りの安全機構で、サーバーが Access-Control-Allow-Origin 応答ヘッダで明示的に許可しない限り、レスポンスの中身を JavaScript に渡さずブロックする。
- 主因は許可ヘッダ不足、OPTIONSが2xxを返さない、credentialsとワイルドカードを併用する3つ。ブラウザのNetworkでOPTIONSと応答ヘッダを確認する。
- 直すのは応答を返すサーバー側。ブラウザは正しいオリジンにだけ許可を出すサーバーを信頼して安全を保つため、フロント側では回避できない。開発時のみプロキシで逃がす。
一言でいうと(このエラーが何を意味するか)
Access to fetch at 'https://api.example.com' from origin 'https://app.example.com' has been blocked by CORS policy は、サーバーの応答は正常に届いているのに、ブラウザがその中身を JavaScript に渡すのを拒否した状態です。ネットワーク障害でも 404 でもありません。
前提となるのが同一オリジンポリシー(Same-Origin Policy)です。オリジンとは「スキーム・ホスト・ポート」の3つ組で、https://app.example.com と https://api.example.com はホストが違うので別オリジンです。ブラウザは、あるオリジンのページが別オリジンの応答を fetch や XMLHttpRequest で読み取ることを既定で禁止します。これがなければ、悪意あるサイトがあなたのログイン済みクッキーを使って銀行 API を叩き、残高を読み取れてしまうためです。
CORS(Cross-Origin Resource Sharing)は、この禁止をサーバー側から選択的に解除する仕組みです。サーバーが Access-Control-Allow-Origin 応答ヘッダで「このオリジンには読ませてよい」と宣言して初めて、ブラウザは中身を渡します。ヘッダが無ければ、通信は 200 でもブラウザ段で握りつぶされます。
リクエストはサーバーに到達し、サーバーは正常に応答しています。curl や Postman では成功するのに、ブラウザだと失敗するのはこのためです。エラーは「読み取り権限が無い」という意味で、サーバーが落ちているわけではありません。
横にスクロール
よくある原因(複数を具体的に、頻度順)
-
サーバーが
Access-Control-Allow-Originを返していない(最多)。API 側で CORS 設定を一切していないケース。応答ヘッダにこの1行が無いだけでブロックされます。 -
プリフライト(OPTIONS)が失敗している。
Content-Type: application/jsonの POST、Authorizationなどのカスタムヘッダ、PUT/DELETE/PATCH などを使うと、ブラウザは本番リクエストの前に OPTIONS メソッドで「この操作を許可するか」を問い合わせます。これをプリフライトと呼びます。サーバーが OPTIONS に 2xx を返さない(404/405 を返す、認証ミドルウェアが先に 401 を返す等)と、本番リクエストは送られずブロックされます。 -
credentials 併用時にワイルドカード(
*)を使っている。クッキーやAuthorizationを送る(fetch(url, { credentials: 'include' }))場合、Access-Control-Allow-Origin: *は仕様で禁止です。具体的なオリジンを1つ返し、かつAccess-Control-Allow-Credentials: trueも返す必要があります。*のままだとThe value of the 'Access-Control-Allow-Origin' header must not be the wildcard '*' when the request's credentials mode is 'include'が出ます。 -
許可ヘッダ/メソッドの宣言漏れ。プリフライトに対し
Access-Control-Allow-HeadersやAccess-Control-Allow-Methodsが不足し、送ろうとしたヘッダ・メソッドがリストに無い。Request header field authorization is not allowed by Access-Control-Allow-Headersはこの典型です。
GET/HEAD/POST で、Content-Type が text/plain application/x-www-form-urlencoded multipart/form-data のいずれか、かつカスタムヘッダが無い場合は「単純リクエスト」となりプリフライトが省かれます。それでも応答に Access-Control-Allow-Origin が無ければブロックされる点は同じです。
診断の手順(切り分け方・見るべきログ/コマンド)
まずサーバー自体が応答するかを、ブラウザ外から確認します。ここで応答が返るなら、問題はサーバーの可用性ではなく CORS ヘッダの有無に絞れます。
# 実オリジンを名乗って応答ヘッダを確認(-i でヘッダ表示)
curl -i -H "Origin: https://app.example.com" https://api.example.com/data
応答に Access-Control-Allow-Origin が含まれるかを見ます。無ければ原因1で確定です。次にプリフライトを手動で再現します。ブラウザが送るのと同じ OPTIONS を投げ、ステータスと許可ヘッダを確認します。
# プリフライトの再現:2xx と Allow-* が返るべき
curl -i -X OPTIONS https://api.example.com/data \
-H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: content-type,authorization"
ブラウザ側では Network タブを開き、失敗したリクエストの直前に同名の OPTIONS リクエストが並んでいないかを確認します。並んでいればプリフライト方式です。その OPTIONS を選び、ステータスコードと Response Headers を読みます。以下の対応で原因が切り分けられます。
| 観察された状態 | 推定原因 | 確認すべきヘッダ |
|---|---|---|
| OPTIONS が無く本番だけ失敗 | 単純リクエストで Allow-Origin 欠落 | Access-Control-Allow-Origin |
| OPTIONS が 404 / 405 / 401 | サーバーが OPTIONS を処理していない | OPTIONS ルーティング・認証の順序 |
| OPTIONS は 200 だが本番で失敗 | Allow-Headers / Allow-Methods 不足 | Access-Control-Allow-Headers / -Methods |
| wildcard の警告文が出る | credentials と * の併用 | Allow-Origin(具体値)+ Allow-Credentials |
解決と予防(対処と再発防止)
直すのは応答を返すサーバー側です。API に CORS ミドルウェアを入れ、許可オリジン・メソッド・ヘッダを明示します。以下は Express の例ですが、考え方はどの言語でも同じです。
// Express: 具体的なオリジンを許可し、プリフライトにも応答する
const cors = require('cors');
app.use(cors({
origin: 'https://app.example.com', // credentials を使うなら * は不可
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true, // クッキー等を送るとき
}));
// cors ミドルウェアが OPTIONS を自動応答する。認証ミドルウェアより前に置く
ミドルウェアを使わず自前で返す場合、プリフライトに必ず 2xx を返すこと、そして credentials を使うなら Allow-Origin に具体値を返すことが要点です。
# credentials 利用時に返すべき応答ヘッダの例
Access-Control-Allow-Origin: https://app.example.com # * は不可
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 600 # プリフライト結果をキャッシュ
複数オリジンを許可したいときは、* を返すのではなく、リクエストの Origin を許可リストと照合し、一致したものだけをそのまま返して Vary: Origin を付けます(キャッシュ汚染を防ぐため)。
ブラウザ拡張で CORS を切る、Access-Control-Allow-Origin: * を常時返す、といった対処は同一オリジンポリシーという防御そのものを外す行為で、CSRF や情報漏洩の穴になります。CORS は攻撃ではなく防御であり、正しく設定して通すのが解です。credentials を使う API で全オリジンを許可することは、特に避けてください。
なぜサーバー側で直すのか。ブラウザは「どのオリジンに読ませてよいか」を、応答を返すサーバーだけが正しく判断できると考えます。もしフロントの JavaScript で許可を上書きできてしまえば、攻撃者のページも同じ手で許可を偽装でき、防御が無意味になります。だから許可の宣言権はサーバーに固定されており、フロント側のコード変更では原理的に解決できません。
開発中に相手サーバーを直せない場合は、開発サーバーのプロキシ(Vite の server.proxy、webpack-dev-server の proxy、Next.js の rewrites 等)で API 呼び出しを同一オリジン経由に見せ、ブラウザに別オリジンと認識させない方法が定石です。これは一時回避であり、本番では上記のサーバー設定が必要です。
CORS の土台である同一オリジンポリシーやブラウザの挙動はWeb、HTTP メソッドとヘッダの仕組みはネットワーク、クッキー・CSRF との関係はセキュリティも参照してください。
- CORS を実施するのはブラウザ。サーバーは通常どおり応答しており、ブロックはクライアント側で起きる
- オリジン=スキーム・ホスト・ポートの3つ組。1つでも違えばクロスオリジン
- プリフライト(OPTIONS)が発生する条件:単純リクエスト以外(JSON の POST、カスタムヘッダ、PUT/DELETE 等)
credentials: 'include'のとき Allow-Origin に*は使えず、具体オリジン+ Allow-Credentials: true が必須
エラー辞典の記事ガイド
CORSエラー(Access-Control-Allow-Origin が無い)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
CORS
比較で見る軸
難易度: advanced / カテゴリ: エラー辞典 / タグ数: 6
導入後に効く点
主因は許可ヘッダ不足、OPTIONSが2xxを返さない、credentialsとワイルドカードを併用する3つ。ブラウザのNetworkでOPTIONSと応答ヘッダを確認する。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- エラー辞典
- タグ数
- 6
判断チェックリスト
- 自社の用途が「CORS / Web」に近いか確認する。
- 強みである「CORS はブラウザが実施するクロスオリジン読み取りの安全機構で、サーバーが Access-Control-Allow-Origin 応答ヘッダで明示的に許可しない限り、レスポンスの中身を JavaScript に渡さずブロックする。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。