CORSエラーの原因と対処法まとめ
CORSエラーは、ブラウザからAPIにリクエストを送ったときに「Access to fetch … has been blocked by CORS policy」というメッセージで表示されるエラーです。Webアプリ開発でAPIを利用する際に頻繁に遭遇します。この記事では、CORSの仕組みを理解したうえで、エラーの原因と具体的な対処法を解説します。
CORSとは何か
CORS(Cross-Origin Resource Sharing、オリジン間リソース共有)は、異なるオリジンへのリクエストをブラウザが制御する仕組みです。セキュリティのために設けられた制限であり、サーバー側の設定で許可する必要があります。
オリジンとは
オリジンは、URLの「スキーム(プロトコル)」「ホスト(ドメイン)」「ポート番号」の3つの組み合わせです。
https://example.com:443/path/page.html
|_____| |_________| |_|
スキーム ホスト ポート
これら3つがすべて一致すれば「同一オリジン」、1つでも異なれば「異なるオリジン(クロスオリジン)」です。
https://example.com と https://example.com/page → 同一オリジン
https://example.com と https://api.example.com → 異なるオリジン(ホストが違う)
https://example.com と http://example.com → 異なるオリジン(スキームが違う)
http://localhost:3000 と http://localhost:8080 → 異なるオリジン(ポートが違う)
Same-Origin Policy(同一オリジンポリシー)
ブラウザには「同一オリジンポリシー」というセキュリティルールがあり、JavaScriptから異なるオリジンへのリクエストのレスポンスを読み取ることをデフォルトで禁止しています。
// http://localhost:3000 で実行しているページから
// http://localhost:8080 のAPIにリクエスト
fetch("http://localhost:8080/api/users")
.then(response => response.json())
.then(data => console.log(data));
// CORSエラーが発生する(オリジンが異なるため)
この制限がないと、悪意のあるサイトがユーザーのブラウザを経由して他のサービスのデータを盗み取ることができてしまいます。
CORSエラーのメッセージを読む
ブラウザのコンソールに表示されるCORSエラーにはいくつかのパターンがあります。
Access-Control-Allow-Originヘッダーがない
Access to fetch at 'http://localhost:8080/api/users'
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ヘッダーが含まれていないことが原因です。
プリフライトリクエストの失敗
Access to fetch at 'http://localhost:8080/api/users'
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リクエスト(プリフライト)にサーバーが適切に応答していないことが原因です。
許可されていないオリジン
Access to fetch at 'http://localhost:8080/api/users'
from origin 'http://localhost:3000' has been blocked by CORS policy:
The 'Access-Control-Allow-Origin' header has a value 'https://example.com'
that is not equal to the supplied origin.
サーバーが許可しているオリジンと、リクエスト元のオリジンが一致していないことが原因です。
対処法1: サーバー側でCORSヘッダーを設定する
CORSエラーの根本的な解決策は、サーバー側で適切なレスポンスヘッダーを返すことです。
基本的なCORSヘッダー
サーバーが返すべきレスポンスヘッダーは次のとおりです。
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Node.js(Express)での設定
const express = require("express");
const app = express();
// 手動でCORSヘッダーを設定
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "http://localhost:3000");
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
res.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
// プリフライトリクエストへの応答
if (req.method === "OPTIONS") {
return res.sendStatus(204);
}
next();
});
corsミドルウェアの利用
Expressではcorsパッケージを使うと簡潔に設定できます。
const express = require("express");
const cors = require("cors");
const app = express();
// 特定のオリジンのみ許可
app.use(cors({
origin: "http://localhost:3000",
methods: ["GET", "POST", "PUT", "DELETE"],
allowedHeaders: ["Content-Type", "Authorization"],
}));
// 複数のオリジンを許可
app.use(cors({
origin: ["http://localhost:3000", "https://example.com"],
}));
ワイルドカード指定の注意点
Access-Control-Allow-Origin: *
ワイルドカード*はすべてのオリジンを許可します。開発中は便利ですが、本番環境ではセキュリティ上の理由から特定のオリジンを指定するのが望ましいです。また、*を指定した場合、credentials: "include"(Cookieの送信)との併用はできません。
対処法2: プロキシを使う
サーバー側の設定を変更できない場合(外部APIを利用する場合など)は、プロキシサーバーを経由する方法があります。
開発サーバーのプロキシ設定
フロントエンド開発でよく使われるツールには、プロキシ機能が組み込まれています。
Viteの場合(vite.config.js):
export default {
server: {
proxy: {
"/api": {
target: "http://localhost:8080",
changeOrigin: true,
},
},
},
};
この設定により、フロントエンドの/api/usersへのリクエストがhttp://localhost:8080/api/usersに転送されます。ブラウザから見ると同一オリジンへのリクエストなので、CORSエラーは発生しません。
フロントエンド側のコード
// プロキシ設定前: 異なるオリジンへ直接リクエスト
// fetch("http://localhost:8080/api/users") // CORSエラー
// プロキシ設定後: 同一オリジンへリクエスト
fetch("/api/users") // プロキシ経由で転送される
.then(response => response.json())
.then(data => console.log(data));
プロキシの仕組み
CORSはブラウザの制限です。サーバー同士の通信にはCORSの制約はありません。開発サーバーのプロキシは、ブラウザからのリクエストをいったん受け取り、サーバー間通信としてAPIにリクエストを転送する仕組みです。
ブラウザ → 開発サーバー(同一オリジン) → APIサーバー(別オリジン)
CORSの制約なし サーバー間通信なのでCORS不要
対処法3: プリフライトリクエストへの対応
特定の条件を満たすリクエスト(「単純リクエスト」でないもの)では、ブラウザが本リクエストの前にOPTIONSメソッドでプリフライトリクエストを送信します。
プリフライトが発生する条件
次のいずれかに該当する場合、プリフライトリクエストが送信されます。
- HTTPメソッドがGET、HEAD、POST以外(PUT、DELETEなど)
Content-Typeがapplication/jsonの場合- カスタムヘッダー(
Authorizationなど)が含まれる場合
// このリクエストはプリフライトが発生する
fetch("http://localhost:8080/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json", // プリフライトの原因
"Authorization": "Bearer token123", // プリフライトの原因
},
body: JSON.stringify({ name: "田中" }),
});
サーバー側のOPTIONS対応
// Expressでの例
app.options("/api/users", (req, res) => {
res.header("Access-Control-Allow-Origin", "http://localhost:3000");
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
res.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
res.header("Access-Control-Max-Age", "86400"); // 24時間キャッシュ
res.sendStatus(204);
});
Access-Control-Max-Ageを設定すると、指定した秒数の間プリフライトの結果がキャッシュされ、同じリクエストに対するプリフライトが省略されます。
よくある間違いと注意点
CORSに関してよくある誤解と注意点をまとめます。
フロントエンド側だけでは解決できない
CORSはブラウザが実施するセキュリティ制御です。フロントエンドのJavaScriptでCORSを回避するヘッダーを追加することはできません。
// NG: フロントエンドでCORSヘッダーを追加しても意味がない
fetch("http://localhost:8080/api/users", {
headers: {
"Access-Control-Allow-Origin": "*", // リクエストヘッダーに入れても無効
},
});
Access-Control-Allow-Originはサーバーが返すレスポンスヘッダーです。
CORSエラーはブラウザの制限
curlやPostmanからは同じAPIに問題なくアクセスできるのに、ブラウザからだけエラーになる場合があります。これはCORSがブラウザ固有のセキュリティ機能だからです。
mode: “no-cors”は解決策にならない
// NG: レスポンスの内容が読めなくなるだけ
fetch("http://localhost:8080/api/users", {
mode: "no-cors",
}).then(response => {
console.log(response.type); // "opaque"
// response.json()は失敗する
});
mode: "no-cors"はCORSエラーを消しますが、レスポンスの内容を読み取れなくなるため、APIからデータを取得する用途では使えません。
まとめ
CORSエラーの仕組みと対処法について解説しました。
- CORSは、異なるオリジン間のリクエストをブラウザが制御するセキュリティの仕組み
- オリジンはスキーム、ホスト、ポート番号の3つで判定される
- サーバー側で
Access-Control-Allow-Originヘッダーを設定するのが根本的な対処法 - サーバーを変更できない場合は、開発サーバーのプロキシ設定を使う
Content-Type: application/jsonやカスタムヘッダーを使うとプリフライトリクエストが発生する- フロントエンド側のコードだけではCORSエラーを解決できない
CORSはセキュリティのための仕組みであり、「回避」するのではなく「正しく許可設定する」のが適切な対処です。エラーメッセージをよく読み、サーバー側で必要なヘッダーを返しているか確認しましょう。