CORSとは?仕組み・プリフライト・サーバー設定例・エラー解決を実装目線で解説
CORS(Cross-Origin Resource Sharing/オリジン間リソース共有)は、ブラウザ上のJavaScriptが自分と異なるオリジンのAPIを呼び出すとき、そのアクセスをサーバー側が許可しているかを確認する仕組みです。フロントエンドとAPIを別ドメインで運用すると、開発中に「Blocked by CORS policy」というエラーで通信が止まります。この記事では、同一オリジンポリシーとの関係、プリフライトリクエストの流れ、Nginx・Apache・Express・Spring Bootでの設定例、そしてエラーの原因切り分けと解決手順までを実装者の視点でまとめました。CORSの概念そのものを整理したい場合は、当社のクロスオリジンリソース共有(CORS)とは何かを先に読むと理解が速くなります。
目次
まとめ:CORS設定はサーバー側・エラー原因はレスポンスヘッダー
CORSはブラウザだけで完結する仕組みではありません。制限をかけるのはブラウザですが、それを緩めて通信を許可するのはサーバー側です。フロントエンドのコードをいくら直してもCORSエラーは消えません。解決の起点は常にAPIサーバーのレスポンスヘッダーにあります。
実装で押さえる順序は3つです。第一に、リクエストがシンプルリクエストかプリフライトを伴うかを見分ける。第二に、サーバーが返すべきAccess-Control-Allow-Originなどのヘッダーを、使うメソッド・ヘッダー・認証情報に合わせて設定する。第三に、エラーメッセージの文言から不足しているヘッダーを逆算する。認証付きリクエストではオリジンにワイルドカード*を使えないなど、単純設定では踏み抜く落とし穴もあわせて示します。
CORSの仕組みと同一オリジンポリシーによるアクセス制限の関係
CORSを理解する起点は、ブラウザが持つ同一オリジンポリシー(Same-Origin Policy)です。この制約があるからこそ、CORSという緩和の仕組みが必要になります。
オリジンを構成するスキーム・ホスト・ポートによる一致判定の基準
オリジンは「スキーム(プロトコル)」「ホスト(ドメイン)」「ポート番号」の3つで決まります。この3つがすべて一致して初めて同一オリジンです。1つでも違えば別オリジン扱いになり、CORSの対象になります。
| 比較対象URL | 基準オリジンとの関係 | 理由 |
|---|---|---|
| https://example.com/api | 同一オリジン | スキーム・ホスト・ポートが一致(パスは無関係) |
| http://example.com | 別オリジン | スキームがhttpで異なる |
| https://api.example.com | 別オリジン | ホスト(サブドメイン)が異なる |
| https://example.com:8080 | 別オリジン | ポート番号が異なる |
実務で多い誤解は「同じドメインなら大丈夫」というものです。www.example.comとapi.example.comはサブドメインが違うため別オリジンで、CORS設定が要ります。ローカル開発でフロントをlocalhost:3000、APIをlocalhost:8000で動かす構成も、ポートが違うため別オリジンです。
ブラウザが別オリジンのレスポンス読み取りを制限する本当の理由
同一オリジンポリシーは、悪意あるサイトが利用者のログインセッションを使って別サービスのAPIを勝手に叩き、その応答を読み取ることを防ぎます。もしこの制約が無ければ、利用者が閲覧中の攻撃サイトから、別タブで開いている社内システムやメールのデータを盗み読みできてしまいます。CORSは「安全な範囲でこの制約を緩める」ための正式な手続きであり、制約を無効化する裏技ではありません。Access-Control-Allow-Originで許可するオリジンを絞るのは、この安全性を保つためです。
レスポンスの読み取りだけを止めリクエスト送信は止めない仕組み
ここは実装者が混乱しやすい点です。CORSはリクエストの送信そのものを止めるとは限りません。単純なリクエストの場合、サーバーには実際にリクエストが届き、処理も実行されます。ブラウザが止めるのは「返ってきたレスポンスをJavaScriptに渡すこと」です。つまりGETやPOSTがサーバー側に到達し、データ更新まで走った後で、フロント側だけがエラーになる状況が起こり得ます。副作用のある操作をGETで実装しない、といった設計上の配慮がここに関わります。
プリフライトリクエストとシンプルリクエストを見分ける判定条件
CORSには2つの経路があります。プリフライトを伴う経路と伴わない経路です。どちらになるかは、送るリクエストの中身によって自動的に決まります。
ブラウザがプリフライトを伴わないシンプルリクエストと判定する条件
次の条件をすべて満たすと、ブラウザは事前確認なしでリクエストを送ります(MDNの定義、2026年7月時点)。
- メソッドが
GET・HEAD・POSTのいずれか - 手動で付けるヘッダーが
Accept・Accept-Language・Content-Language・Content-Typeなど限られたものだけ Content-Typeがapplication/x-www-form-urlencoded・multipart/form-data・text/plainのいずれか
逆に言えば、JSON APIでよく使うContent-Type: application/jsonを付けた時点でシンプルリクエストの条件を外れ、プリフライトが発生します。認証トークンをAuthorizationヘッダーで送る場合も同じです。現代のSPAとJSON APIの組み合わせは、ほぼプリフライトを伴うと考えて設計するのが実務的です。
プリフライトで先に飛ぶOPTIONSリクエストの中身と確認内容
プリフライトでは、本来のリクエストの前にOPTIONSメソッドの問い合わせが自動で飛びます。ブラウザは「これからこのメソッド・このヘッダーで送りたいが許可されるか」をサーバーに確認します。
- ブラウザが
OPTIONS /api/usersを送り、Access-Control-Request-Method(実行したいメソッド)とAccess-Control-Request-Headers(付けたいヘッダー)を添える - サーバーが
Access-Control-Allow-Methods・Access-Control-Allow-Headers・Access-Control-Allow-Originを返して許可範囲を答える - 許可範囲に収まっていれば、ブラウザが本来のリクエスト(PUTやDELETEなど)を送る
この仕組みのため、CORS対応ではOPTIONSへの応答を必ず用意する必要があります。本来のGETやPOSTだけ設定してOPTIONSを404や405で返す実装は、プリフライトの段階で失敗します。
Access-Control-Max-Ageでプリフライト回数を抑える設定
プリフライトはリクエストごとに往復が1回増えるため、呼び出しが多いAPIでは体感速度に響きます。サーバーがAccess-Control-Max-Ageを返すと、ブラウザはその秒数だけプリフライト結果をキャッシュし、同じ条件の再確認を省きます。Chromium系ブラウザは上限を7200秒(2時間)に丸める仕様があり、それより大きい値を設定しても頭打ちです。まず数百秒から始め、設定変更の反映を確認しながら伸ばす運用が無難です。
サーバー別に見るCORS設定の実装例とヘッダー集約の基本方針
ここが競合記事の手薄な部分であり、実装者が最も知りたい箇所です。CORSヘッダーはリバースプロキシ・Webサーバー・アプリケーションのどの層でも設定できます。層を分けて管理すると設定の重複や矛盾が起きるため、原則としてどこか1か所に集約します。
Nginxでリバースプロキシ層にCORSヘッダーを付与する設定例
APIの前段にNginxを置く構成では、Nginx側でヘッダーを付与できます。プリフライトのOPTIONSを204で早期に返すのが定石です。
location /api/ {
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin "https://app.example.com";
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE";
add_header Access-Control-Allow-Headers "Authorization, Content-Type";
add_header Access-Control-Max-Age 600;
return 204;
}
add_header Access-Control-Allow-Origin "https://app.example.com" always;
}
ポイントは、通常レスポンスへのadd_headerにalwaysを付ける点です。これを省くと、4xxや5xxのエラーレスポンスにCORSヘッダーが付かず、エラー時だけブラウザがCORS違反として二重に分かりにくいエラーを出します。オリジンは実在するフロントのURLを指定し、複数許可したい場合はmapディレクティブで許可リストを持つ方法が確実です。
Apacheのmod_headersでCORSヘッダーを設定する記述例
Apacheではmod_headersを有効にしたうえで、.htaccessまたは仮想ホスト設定にヘッダーを記述します。
Header set Access-Control-Allow-Origin "https://app.example.com"
Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE"
Header set Access-Control-Allow-Headers "Authorization, Content-Type"
ApacheでもOPTIONSへの応答を用意する必要があります。RewriteRuleでOPTIONSメソッドを200で返すルールを併記するのが一般的な対処です。単一オリジン固定なら上記で足りますが、動的に許可オリジンを判定したい場合はアプリケーション層に寄せたほうが管理しやすくなります。
ExpressとSpring Bootでのアプリケーション層のCORS設定
Node.jsのExpressでは公式に近いcorsミドルウェアが広く使われます。オリジンを配列で渡し、認証情報を通す場合はcredentialsを有効にします。
app.use(cors({
origin: ["https://app.example.com"],
methods: ["GET", "POST", "PUT", "DELETE"],
allowedHeaders: ["Authorization", "Content-Type"],
credentials: true
}));
Spring Bootでは@CrossOriginアノテーションをコントローラーに付ける方法と、WebMvcConfigurerで全体設定する方法があります。全体設定ならaddCorsMappingsでallowedOrigins・allowedMethods・allowCredentialsを一括指定でき、エンドポイントごとの付け忘れを防げます。どのフレームワークでも、設定値の実体は同じHTTPレスポンスヘッダーである点は変わりません。フレームワークが違っても、ブラウザに届くのはAccess-Control-Allow-Originなどのヘッダーです。
ブラウザのエラーメッセージ別に見るCORS原因切り分けと解決手順
「Blocked by CORS policy」という一文だけでは原因は分かりません。ブラウザの開発者ツールに出るエラー文の後半に、不足しているものが必ず書かれています。文言ごとに対処を対応させます。
ブラウザの開発者ツールに出るエラーメッセージ別の原因と対処法
| エラー文(抜粋) | 原因 | 対処 |
|---|---|---|
| No Allow-Origin header | 許可ヘッダー未返却 | サーバーでヘッダー設定 |
| origin does not match | 許可オリジン不一致 | 末尾やhttpsの綴りを確認 |
| Method PUT is not allowed | 許可メソッド不足 | Allow-Methodsに追加 |
| header is not allowed | 許可ヘッダー不足 | Allow-Headersに追加 |
| credentials mode error | 認証付きで*を使用 | *を具体オリジンに変更 |
切り分けの順序は、まずネットワークタブでOPTIONSリクエストの有無とそのステータスを見ることです。OPTIONSが失敗していればプリフライト設定の問題、OPTIONSは通るのに本リクエストで落ちるなら許可メソッドやヘッダーの不足、と切り分けられます。
ワイルドカードとCredentialsを併用できない仕様上の制約
認証を伴うAPIで最も踏みやすい落とし穴がこれです。Access-Control-Allow-Origin: *と、Cookieやトークンを送るcredentials: includeは同時に成立しません。仕様上、Credentialsを許可する場合はオリジンを*ではなく具体的な値で返し、Access-Control-Allow-Credentials: trueを添える必要があります。複数オリジンを許可したいときは、リクエストのOriginヘッダーを見て許可リストに含まれるならそのオリジンをそのまま返す、という動的処理をサーバー側で実装します。ここは静的な*設定では解決できません。
開発中のプロキシによるCORS回避を本番へ持ち込まない実装判断
ここは立場を明確にします。ローカル開発では、開発サーバーのプロキシ機能(Viteのserver.proxyなど)でフロントとAPIを同一オリジンに見せ、CORSを回避する手法が使えます。これは開発の手数を減らす有効な手段です。ただし本番でこの回避に頼るのは避けるべきです。プロキシ回避はCORSを設定不要にしているのではなく、同一オリジンに見せて発生させないだけで、本番のドメインが別オリジンなら結局サーバー設定が要ります。開発中だけ通って本番で失敗する典型パターンがこれです。ブラウザ拡張でCORSを無効化する確認も、自分の環境でしか通らず、動作確認の証拠になりません。これを判断基準にします。CORSヘッダーはWebサーバーやリバースプロキシ、APIゲートウェイで設定する領域です。フロントとAPIを別ドメインで運用する構成なら、設計段階からサーバー側の許可設計を織り込むと後戻りを防げます。こうしたサーバー構成やクラウド基盤の設計を外部に相談したい場合は、AWS・GCP・Azureのインフラ構築で、CORS設定を含むAPIサーバーの設計から対応できます。
よくある質問
CORSの実装でつまずきやすい点を、検索されやすい疑問の形でまとめます。
CORSエラーはフロントエンドのコードで直せますか?
直せません。CORSの許可を出すのはAPIサーバー側です。フロントでfetchのオプションを変えても、サーバーがAccess-Control-Allow-Originを返さなければエラーは解消しません。例外は、フロントとAPIを同一オリジンにまとめる構成変更ですが、これはコード修正ではなく配置の変更です。
CORSの読み方は何ですか?
「コルス」と読まれることが一般的です。Cross-Origin Resource Sharingの略で、日本語ではオリジン間リソース共有と訳されます。仕様の正式な定義はWHATWGのFetch標準にまとめられています。
Access-Control-Allow-Originに複数のオリジンを指定できますか?
ヘッダーに複数オリジンをカンマ区切りで並べる書き方は仕様上サポートされていません。複数を許可したい場合は、サーバーでリクエストのOriginヘッダーを読み、許可リストに含まれていればそのオリジンだけを動的に返す実装にします。ワイルドカード*は全許可になり、認証付きリクエストでは使えない制約もあるため、許可範囲は明示的に絞るのが安全です。
プリフライトリクエストを無くすことはできますか?
リクエストをシンプルリクエストの条件内に収めれば発生しません。ただしJSON APIでContent-Type: application/jsonや認証ヘッダーを使うと条件を外れ、プリフライトが飛びます。現実的には無くすより、Access-Control-Max-Ageでプリフライト結果をキャッシュして回数を減らす方向が実装しやすい対処です。
CORSはセキュリティ対策として十分ですか?
CORSは同一オリジンポリシーの緩和ルールであって、それ単体でAPIを守る認証・認可の仕組みではありません。CORSで許可したオリジンからのアクセスでも、サーバー側の認証・入力検証は別途必要です。CORS設定を緩くすればセキュリティが下がりますが、厳しくすれば安全になるわけでもなく、あくまで「どのオリジンにレスポンスを読ませるか」を制御する層だと位置づけるのが正確です。
関連記事
- クロスオリジンリソース共有(CORS)とは何か?その基本概念と重要性を解説:CORSの概念・同一オリジンポリシーの背景を、実装より前の基礎理解として整理した記事です。仕組みからつかみたい場合の入口になります。
- インフラ構築(AWS/GCP/Azure):CORSヘッダーを設定するWebサーバー・リバースプロキシ・APIゲートウェイの構築を含む、クラウド基盤の設計・実装サービスです。