CORSエラーの直し方:ブラウザでAPI通信が止まる理由と確認手順
ブラウザ上のJavaScriptから別ドメインのAPIを呼び出したときに通信が止まるなら、多くの場合はAPIサーバー側のCORS設定を確認します。fetch() の書き方だけを変えても、サーバーが許可ヘッダーを返さなければブラウザはレスポンスをJavaScriptへ渡しません。
CORSは「別のオリジンから来たブラウザのリクエストを、サーバーが読むことを許可する仕組み」です。WordPressのテーマや管理画面カスタマイズ、静的サイト、フロントエンドアプリから外部APIを読む場面でよく出ます。
この記事では、2026年7月時点のMDN Web DocsとFetch Standardを前提に、ブラウザで起きるCORSエラーの見方、最小の設定例、やってはいけない対応を整理します。
- CORSエラーは、APIが落ちているエラーとは限らない
- 許可するのは、リクエストを送る側ではなくAPIを返すサーバー側
Access-Control-Allow-Origin、OPTIONS、認証情報の扱いを分けて見る- APIキーやCookieを使う場合は、安易に
*を使わない
CORSエラーは「ブラウザが止めている」エラー
CORSエラーの核心は、通信先サーバーのレスポンスをブラウザがJavaScriptへ渡してよいか判定している点です。
たとえば、次のような構成を考えます。
- 表示中のページ:
https://example.com - 呼び出したいAPI:
https://api.example.net/items - 実行するコード: ブラウザ上の
fetch()
fetch("https://api.example.net/items")
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error(error));
このとき、example.com と api.example.net はオリジンが異なります。オリジンは、ざっくり言えば次の3つの組み合わせです。
- スキーム:
httpsかhttpか - ホスト:
example.comなどのドメイン - ポート:
443、3000など
MDNは、CORSをHTTPヘッダーにもとづく仕組みとして説明しています。サーバーは「このオリジンからのブラウザ読み取りを許可する」とレスポンスヘッダーで示します。
ここがポイント: CORSは「APIにリクエストできるか」だけでなく、「ブラウザ上のJavaScriptがレスポンスを読めるか」を制御します。
まず見るべきエラーメッセージ
最初に見る場所は、ブラウザの開発者ツールです。JavaScriptの catch だけでは詳しい理由が分かりにくいため、ConsoleとNetworkを両方確認します。
よく見るメッセージは次のようなものです。
Access to fetch at 'https://api.example.net/items'
from origin 'https://example.com' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
この場合、見るべき点は明確です。
- 呼び出し元のオリジンは
https://example.com - 呼び出し先は
https://api.example.net/items - APIレスポンスに
Access-Control-Allow-Originがない - そのためブラウザがレスポンスの読み取りを止めている
Networkタブでは、対象リクエストを開いて次を確認します。
- Request Headers に
Originが付いているか - Response Headers に
Access-Control-Allow-Originがあるか - ステータスコードが
200、204、401、403、500のどれか OPTIONSリクエストが先に送られていないか
CORSエラーに見えても、実際には認証エラーやサーバーエラーが先に起きていることがあります。ただし、サーバーエラー時にもCORSヘッダーが返っていないと、ブラウザの画面上ではCORSエラーに見えます。
基本対応はAPIサーバーで許可ヘッダーを返すこと
最小の対応は、APIサーバーのレスポンスに Access-Control-Allow-Origin を付けることです。
公開してよい読み取り専用APIなら、次のように全オリジンを許可する場合があります。
Access-Control-Allow-Origin: *
ただし、実務では許可するオリジンを絞ることが多いです。
Access-Control-Allow-Origin: https://example.com
NG例: フロント側だけで直そうとする
次のように、ブラウザ側の fetch() にレスポンスヘッダーを書いてもCORS設定にはなりません。
fetch("https://api.example.net/items", {
headers: {
"Access-Control-Allow-Origin": "*"
}
});
Access-Control-Allow-Origin は、APIサーバーがレスポンスで返すヘッダーです。ブラウザからリクエストヘッダーとして送るものではありません。
改善例: サーバー側で返す
PHPで簡易APIを作っているなら、レスポンスを返す前にヘッダーを出します。
<?php
header('Access-Control-Allow-Origin: https://example.com');
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'status' => 'ok',
'items' => ['apple', 'orange']
]);
入力例として、https://example.com のブラウザからこのAPIを呼び出します。
const response = await fetch("https://api.example.net/items.php");
const data = await response.json();
console.log(data);
期待される出力は次の形です。
{
"status": "ok",
"items": ["apple", "orange"]
}
WordPressサイトから外部APIを読む場合も考え方は同じです。テーマやブロック側のJavaScriptで外部APIを直接呼ぶなら、外部API側がそのWordPressサイトのオリジンを許可している必要があります。
外部APIのCORS設定を変更できない場合は、WordPressのサーバー側でAPIを呼び、ブラウザには自サイトのREST APIやAjaxエンドポイントとして返す方法を検討します。これは「ブラウザから外部APIへ直接読む」形ではなく、「自サイトのサーバーが外部APIと通信する」形に変える対応です。
OPTIONSが出るときはプリフライトを疑う
POST、PUT、独自ヘッダー、特定の Content-Type を使うと、ブラウザは本番のリクエスト前に OPTIONS リクエストを送ることがあります。これをプリフライトリクエストと呼びます。
たとえば次のコードは、Authorization ヘッダーを付けてJSONを送っています。
await fetch("https://api.example.net/orders", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer dummy-token"
},
body: JSON.stringify({ itemId: 123, quantity: 2 })
});
この場合、ブラウザは先に次のような確認を送ります。
OPTIONS /orders HTTP/1.1
Origin: https://example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization,content-type
APIサーバーは、少なくとも次のように返す必要があります。
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: POST, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
ここで OPTIONS に対応していないと、本番の POST は送られません。Networkタブで POST が見えず OPTIONS だけ失敗しているなら、API本体の処理より先にプリフライト対応を確認します。
プリフライトでよくある失敗
- サーバーやWAFが
OPTIONSを拒否している Access-Control-Allow-Methodsに実際のメソッドがないAccess-Control-Allow-HeadersにAuthorizationやContent-Typeがない- リダイレクト先でCORSヘッダーが消えている
- エラー時のレスポンスだけCORSヘッダーが付いていない
特にWordPressやCMSの裏にCDN、リバースプロキシ、セキュリティプラグインがある構成では、アプリケーションコードまで届く前に OPTIONS が止まることがあります。
Cookieや認証情報を送る場合は * では足りない
ログイン状態やCookieを使うAPIでは、CORS設定をより厳密にします。Access-Control-Allow-Origin: * と認証情報付きリクエストは組み合わせられません。
ブラウザ側でCookieを含める例です。
const response = await fetch("https://api.example.net/me", {
credentials: "include"
});
この場合、サーバー側は次のように具体的なオリジンを返します。
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Credentials: true
注意点は3つあります。
Access-Control-Allow-Origin: *にしない- 許可するオリジンを固定、または安全な許可リストで判定する
- CookieやAPIキーをブラウザに置く必要があるか見直す
APIキーを使う場合、ブラウザのJavaScriptに秘密キーを直接書くのは避けます。公開ページのソースや開発者ツールから見えるためです。必要ならサーバー側でAPIを呼び出し、ブラウザには必要な結果だけを返します。
CORSとHTTPエラーを切り分ける確認手順
CORS対応では、原因を一つずつ外すのが早道です。闇雲にヘッダーを増やすより、どの段階で止まっているかを見ます。
- ConsoleのCORSエラー文を読む
- Networkで対象URLとステータスコードを見る
OPTIONSがあるか確認する- Response Headersに
Access-Control-Allow-Originがあるか見る - 認証ありなら
Access-Control-Allow-CredentialsとCookie設定を見る - APIサーバー、CDN、プロキシ、WAFのどこでヘッダーが付くか確認する
ローカル開発では、次のような組み合わせでも別オリジンになります。
http://localhost:3000からhttp://localhost:8000http://127.0.0.1:3000からhttp://localhost:3000http://example.comからhttps://example.com
同じPC上の開発環境でも、ポートやホスト名が違えばCORSの対象になります。
代表的な対応パターンの使い分け
CORSは「とりあえず全部許可」で済ませると、後で認証情報や管理系APIを扱うときに危険です。用途ごとに対応を分けます。
| 場面 | 向いている対応 | 注意点 |
|---|---|---|
| 公開データを読むだけ | Access-Control-Allow-Origin: * | Cookieや認証情報を使わない前提にする |
| 自社サイトからだけ読む | 許可オリジンを具体的に指定 | 本番、ステージング、開発環境のURLを分けて管理する |
| ログイン状態でAPIを読む | 具体的なオリジン + Access-Control-Allow-Credentials: true | * は使わず、Cookie設定も確認する |
| 外部APIのCORSを変更できない | 自分のサーバーを経由する | APIキーをサーバー側で管理し、返すデータを絞る |
| 管理画面や社内ツール | 許可リスト、認証、CSRF対策を合わせて設計 | CORSだけをセキュリティ対策として扱わない |
見落とされがちなのは、CORSは「アクセス制御のすべて」ではない点です。CORSヘッダーを絞っても、サーバーAPI自体が認証なしで重要データを返すなら危険です。逆に、CORSで止まっていても、サーバー間通信やcurlからは呼べることがあります。
すぐ使える最小チェックリスト
最後に、実務でCORSエラーを見たときの確認項目をまとめます。
- 呼び出し元と呼び出し先のオリジンを正確に書き出す
- APIサーバーのレスポンスに
Access-Control-Allow-Originがあるか確認する OPTIONSが失敗しているなら、プリフライト対応を先に直すAuthorization、Content-Type: application/json、独自ヘッダーを使っているか見る- Cookieを使うなら
credentials: "include"とAccess-Control-Allow-Credentialsをセットで確認する *でよいのは、認証情報を含まない公開APIなどに限る- 外部APIの設定を変えられないなら、サーバー側プロキシやバックエンド経由を検討する
CORSエラーの次の分岐点は、「APIサーバーを自分で設定できるか」です。設定できるなら、許可するオリジン、メソッド、ヘッダーを必要最小限で返します。設定できないなら、ブラウザから直接読む設計をやめ、自分のサーバー側でAPI通信を受け持たせるのが現実的な対応になります。
