CORS(オリジン間リソース共有)
別オリジンのデータを fetch すると出る、あの赤いエラーの正体。ブラウザの安全装置(同一オリジンポリシー)を、サーバ側のヘッダで“例外的に許可”する仕組み。
- 1.CORS は「別オリジンへのアクセスをサーバが許可する」仕組み。判定して許可を出すのはブラウザ+サーバで、フロントのコードでは消せない。
- 2.鍵は HTTP レスポンスの Access-Control-Allow-Origin ヘッダ。これが無い/合わないとブラウザがレスポンスを読ませず、エラーになる。
- 3.PUT や独自ヘッダなど“危ない”リクエストは、本番の前に OPTIONS(プリフライト)で「送っていい?」と事前確認が走る。
ポイントは、CORS はサーバのアクセスを増やす緩和策であって、制限を強める仕組みではないこと。そして判定するのはブラウザだということです。だから「JS のコードを直せば直る」ものではありません。
オリジンとは(scheme / host / port)
「オリジン(origin)」は、URL のうち スキーム・ホスト・ポートの3点セットを指します。この3つがすべて一致して初めて「同一オリジン」です。
https://example.com:443/page を基準にすると…
| 相手の URL | 同一オリジン? | 理由 |
|---|---|---|
| https://example.com/other | ✅ 同一 | scheme/host/port が一致(443 は https の既定) |
| http://example.com/ | ❌ 別オリジン | scheme が http で違う |
| https://api.example.com/ | ❌ 別オリジン | host(サブドメイン)が違う |
| https://example.com:8443/ | ❌ 別オリジン | port が違う |
オリジンの判定にパス・クエリ・フラグメントは含みません。/a と /b は同一オリジン。逆に http と https の違い、example.com と www.example.com の違いは別オリジン扱いです。ここを取り違えると、なぜCORSが出るのか分からなくなります。
同一オリジンポリシーと、なぜ CORS が要る
ブラウザには 同一オリジンポリシー(Same-Origin Policy) という安全装置があります。あるオリジンの JavaScript が、別オリジンのレスポンスの中身を勝手に読むことを禁じるルールです。
なぜ必要か。もし制限が無ければ、あなたが開いている悪意あるサイトの JS が、裏で https://bank.example.com/balance をあなたのログイン状態(Cookie)込みで叩き、残高を読み取って盗み出せてしまいます。これを防ぐのが同一オリジンポリシーです。
一方で現実には、https://app.example.com のフロントが https://api.example.com の API を呼ぶ、といった正当な別オリジン通信が必要です。そこで「サーバ側が明示的に許可したものだけ通す」仕組みとして CORS が用意されています。
よくある誤解です。単純リクエストの場合、ブラウザはリクエスト自体はサーバに送っています。サーバの処理(DB更新など)は走り得ます。ブラウザがやっているのは「許可ヘッダが無いレスポンスを、JS に読ませない」こと。だから CORS は“盗み見の防止”であって、“サーバへの到達の防止”ではありません(プリフライトが付く場合は事前に止まります)。
単純リクエストとプリフライト
CORS には大きく2パターンあります。リクエストが「単純(simple)」かどうかで分岐します。
単純リクエストの主な条件(ざっくり):
- メソッドが
GET/POST/HEADのいずれか - 独自ヘッダを付けない(
Authorizationなど追加のものは原則NG) Content-Typeがapplication/x-www-form-urlencoded/multipart/form-data/text/plainのいずれか
これらを1つでも外れると「単純でない」とみなされ、本番リクエストの前に プリフライト(preflight) が走ります。プリフライトは OPTIONS メソッドで「これから本番を送りたい。許可する?」とサーバに事前確認する仕組みです。
| 観点 | 単純リクエスト | プリフライトを伴うリクエスト |
|---|---|---|
| きっかけ | GET/POST 等+安全なヘッダのみ | PUT/DELETE、独自ヘッダ、application/json など |
| 往復回数 | 本番1回のみ | OPTIONS(事前確認)→ 本番 の2回 |
| 事前確認 | なし(いきなり本番) | OPTIONS でサーバの許可を確認してから本番 |
| 副作用 | 本番が即サーバに届く | 許可されなければ本番は送られない |
モダンな API でありがちな Content-Type: application/json の POST は、単純リクエストの条件を外れます。結果、必ずプリフライト(OPTIONS)が先に飛びます。「GET は通るのに POST だけ落ちる」と感じたら、まず OPTIONS が許可されているかを疑いましょう。
Access-Control-Allow-* ヘッダ
許可を出すのはサーバのレスポンスヘッダです。代表的なものを押さえれば全体像がつかめます。
| ヘッダ | 付ける場所 | 意味 |
|---|---|---|
| Access-Control-Allow-Origin | 本番+OPTIONS | どのオリジンを許可するか(例: https://app.example.com / *) |
| Access-Control-Allow-Methods | OPTIONS の応答 | 許可するメソッド(GET, POST, PUT…) |
| Access-Control-Allow-Headers | OPTIONS の応答 | 許可する独自ヘッダ(Authorization, Content-Type…) |
| Access-Control-Allow-Credentials | 本番+OPTIONS | Cookie 等の資格情報付きを許可するか(true) |
| Access-Control-Max-Age | OPTIONS の応答 | プリフライト結果をキャッシュする秒数 |
プリフライトでは、ブラウザが Access-Control-Request-Method / Access-Control-Request-Headers で「こういう本番を送りたい」と申告し、サーバが上記の Allow-* で「OK / NG」を返す、という対話になっています。
資格情報(Cookie やクライアント証明書)を送る場合、Access-Control-Allow-Origin: * は使えません。許可オリジンを https://app.example.com のように具体名で明記し、かつ Access-Control-Allow-Credentials: true が必要です。さらにフロント側も fetch(url, { credentials: "include" }) のように資格情報送信を指定します。ここは仕様で固められたセキュリティ要件で、抜け道はありません。
典型的なエラーと対処
ブラウザのコンソールに出る代表的なメッセージと、原因の切り分けです。
| 症状(コンソール) | 本当の原因 | 対処 |
|---|---|---|
| No 'Access-Control-Allow-Origin' header is present | サーバが許可ヘッダを返していない | サーバ側で対象オリジンに Allow-Origin を付与 |
| does not match (Origin が許可リストに無い) | Allow-Origin の値が呼び出し元と不一致 | 許可オリジンに自分の origin を追加(http/https・サブドメイン注意) |
| Response to preflight... 403/404 | OPTIONS をサーバが処理していない | OPTIONS ルートを実装し 2xx+Allow-* を返す |
| credentials... wildcard not allowed | * と Cookie を併用している | オリジンを具体名にし Allow-Credentials: true を返す |
CORS はブラウザがサーバの許可を確認する仕組みなので、原則としてサーバ側のヘッダ設定で直すものです。フロントの mode: 'no-cors' は一見回避策に見えますが、レスポンスの中身を読めなくなる(opaque になる)ため、API 呼び出しの解決策にはなりません。どうしてもサーバを直せない時の最終手段が、自前のバックエンド経由で中継するプロキシです(ブラウザを介さないサーバ間通信に CORS は適用されないため)。
例:許可するサーバと、呼ぶ側
サーバが返すべきレスポンスヘッダ(イメージ)。
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 600
Content-Type: application/json
プリフライトが走るときの、ブラウザ → サーバの OPTIONS(イメージ)。
OPTIONS /items HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization
呼ぶ側の JavaScript。Cookie を送るなら credentials の指定を忘れずに。
// JSON の PUT → 自動でプリフライト(OPTIONS)が先に飛ぶ
const res = await fetch("https://api.example.com/items/1", {
method: "PUT",
headers: { "Content-Type": "application/json" },
credentials: "include", // Cookie 等を送る場合
body: JSON.stringify({ name: "demo" }),
});
const data = await res.json();
CORS はあくまでブラウザの世界の話です。土台となる HTTP の仕組みは HTTP / HTTPS を、ブラウザから API を叩く側の作法は REST API もあわせてどうぞ。
Web/フロントエンド Article
CORS(オリジン間リソース共有)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
CORS
比較で見る軸
難易度: intermediate / カテゴリ: Web/フロントエンド / タグ数: 4
導入後に効く点
鍵は HTTP レスポンスの Access-Control-Allow-Origin ヘッダ。これが無い/合わないとブラウザがレスポンスを読ませず、エラーになる。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- intermediate
- カテゴリ
- Web/フロントエンド
- タグ数
- 4
判断チェックリスト
- 自社の用途が「CORS / Web」に近いか確認する。
- 強みである「CORS は「別オリジンへのアクセスをサーバが許可する」仕組み。判定して許可を出すのはブラウザ+サーバで、フロントのコードでは消せない。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。