ReactにおけるCORS:その概要、重要性、そして有効化の方法
ReactにおけるCORSの理解
CORSは長い間、開発者、とくに初心者にとって混乱とフラストレーションの原因となってきました。この概念は、特にReact、Angular、Vueのようなフレームワークでシングルページアプリケーション(SPA)を構築し、第三者製のAPIと連携しようとする場合に、理解が難しいものとなりがちです。
このガイドでは、CORSを基礎から理解できるように説明します。Reactの簡単なアプリとExpressサーバーを一緒に構築して、CORSエラーが何によって引き起こされ、なぜ発生するのかを解説します。さらに重要なのは、これらの問題を解決するさまざまな方法を、一般的なケースとReact環境に特化したケースの両方について順を追って紹介します。
CORSとは何か?
CORS、すなわちCross-Origin Resource Sharing(クロスオリジンリソース共有)は、Webアプリケーションが異なるオリジンでホストされているサーバーからリソースを要求する方法を規定するプロトコルです。HTTPSが安全な通信のルールを定義するのと同様に、CORSはクロスオリジンリクエストのルールを定めます。
現代のWebアプリは通常、クライアント(ブラウザで動作するフロントエンド)とサーバー(通常はAPIまたはバックエンドサービス)という2つの主要な部分に分かれています。クライアントはサーバーにリクエストを送信し(たとえばデータを取得するため)、サーバーはレスポンスを返します。これら2つの部分が異なるドメイン、ポート、またはプロトコル上にある場合に、CORSが登場します。
なぜこのアーキテクチャが一般的なのか
フロントエンドとバックエンドを別々のアプリケーションとして開発・デプロイするこの疎結合アーキテクチャは、ますます一般的になっています。大きな利点のひとつは柔軟性です。バックエンドは、Webアプリ、デスクトップインターフェース、モバイルアプリ、さらにはIoTデバイスなど、さまざまなタイプのクライアントに対応できます。各クライアントは、プレゼンテーションレイヤーと強く結びつくことなく、同じAPIを利用できます。
同一オリジンポリシーとクロスオリジンリクエスト
クライアントとサーバーは別々のアプリケーションであるため、通常は異なるドメイン、ポート、またはプロトコルでホストされます。つまり、自分のフロントエンドが自分のバックエンドと通信しようとする場合でも、ブラウザはそのリクエストをクロスオリジンとして扱うことがあります。
これは、認証、アナリティクス、決済ゲートウェイなどの第三者製サービスを利用する場合にさらに一般的になります。これらのどの場合でも、フロントエンドはHTTPリクエストを送信して、別のオリジンとやり取りする必要があります。
そしてここに落とし穴があります。現代のブラウザは、Same-Origin Policy(同一オリジンポリシー)と呼ばれるセキュリティ機能を適用します。これは、あるオリジンで実行されるスクリプトが、別のオリジンのリソースとどのようにやり取りできるかを制限するものです。そこでCORSが登場します。それは、これらのクロスオリジンリクエストを安全に有効にするメカニズムです。
なぜブラウザがクロスオリジンリクエストをブロックするのか
Webアプリケーションが異なるオリジン(異なるドメイン、ポート、プロトコルなど)のリソースを要求しようとすると、ブラウザはSame-Origin Policy(SOP:同一オリジンポリシー)として知られるセキュリティ機能を適用します。このポリシーは、悪意のある可能性のあるWebサイトが、別のオリジン上の機密データに許可なくアクセスするのを防ぐように設計されています。
歴史的に、これによりWebはずっと安全になりました。たとえば、xyz.comで実行されるスクリプトがabcbank.comから個人データをこっそり取得することはできず、ユーザーをクロスサイト攻撃から保護していました。しかし、同一オリジンポリシーは、正当な使用例、たとえばlocalhost:3000上のReactアプリがlocalhost:8080や外部サービスで実行されているAPIからデータを取得しようとする場合もブロックします。
CORSの登場:SOPの制限に対する解決策
ここでCORS—Cross-Origin Resource Sharing(クロスオリジンリソース共有)—が登場します。
CORSは、管理された条件下で同一オリジンポリシーを緩和するプロトコルです。サーバーが特定のHTTPヘッダーを通じて、特定のクロスオリジンリクエストが安全で許可されることを示すことを可能にします。
クライアントアプリケーションが別のオリジンにリクエストを行うと、サーバーはCORSヘッダーの特別なセットで応答できます。これらのヘッダーは、ブラウザに「このクロスオリジンリクエストは許可されています」と伝える「許可証」のような役割を果たします。その結果、ブラウザはレスポンスをブロックしなくなり、リソースは正常に共有されます。
CORSが有効な場合はどうなるのか?
ブラウザがサーバーのレスポンス内のCORSヘッダーを検出すると、リクエストのオリジンが異なっていても、アプリケーションによるデータへのアクセスを許可します。それがCORSの本質です。すなわち、オリジン間でのリソースへの管理されたアクセスです。
CORSが何であり、なぜ必要かについてしっかり理解できたところで、実際に動作を確認するための実践的な例を順を追って見ていきましょう。さらに深く掘り下げたい場合は、いつでも詳細なCORSガイドで追加の情報を確認できます。
🛠️ ステップ1:APIエンドポイントを持つExpressサーバーを作成する
CORSがどのように機能するかを示すためには、以下が必要です。
-
HTTPリクエストを送信するクライアント(Reactで構築)
-
それらのリクエストに応答するAPIエンドポイントを公開するサーバー(Expressで構築)
⚠️ 実際のCORSシナリオを引き起こすには、クライアントとサーバーが異なるオリジンで実行される必要があります。たとえば、localhost:3000やlocalhost:8080のような異なるポートです。
🧱 サーバーをセットアップする
まず、基本的なExpressサーバーを作成することから始めましょう。
-
プロジェクトフォルダーを作成する:
mkdir cors-server && cd cors-server
-
新しいNode.jsプロジェクトを初期化する:
npm init -y
これにより、デフォルト値を含むpackage.jsonファイルが作成されます。
-
Expressをインストールする:
npm install express
-
アプリのエントリファイルを作成する:
ルートディレクトリにapp.jsという名前のファイルを作成し、次のコードを追加します。
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Welcome to CORS server 😁');
});
app.get('/cors', (req, res) => {
res.send('This has CORS enabled 🎈');
});
app.listen(8080, () => {
console.log('Listening on port 8080');
});
これは、2つのエンドポイントを持つ最小限のExpressサーバーです。
-
/はウェルカムメッセージを返します。 -
/corsは、Reactアプリから取得するリソースエンドポイントをシミュレートします。
-
サーバーを実行する:
node app
起動したら、ブラウザでhttp://localhost:8080/にアクセスしてください。次のような画面が表示されるはずです。
Welcome to CORS server 😁
また、http://localhost:8080/corsにアクセスすると、次のような画面が表示されるはずです。
⚛️ ステップ2:Reactアプリをセットアップする
Expressサーバーが起動して動作しているので、そのサーバーにHTTPリクエストを送信する簡単なReactアプリを作成しましょう。そして、その修正方法を学ぶために、意図的にCORSエラーを発生させます。
📦 新しいReactプロジェクトを作成する
サーバーとは別のディレクトリで、次のコマンドを実行します。
npx create-react-app react-cors-guide
これにより、react-cors-guideというフォルダーに基本的なReactアプリが生成されます。
完了したら、プロジェクトディレクトリに移動してsrc/App.jsファイルを開きます。その内容を次のコードに置き換えてください。
import { useEffect } from 'react';
import './App.css';
function App() {
const makeAPICall = async () => {
try {
const response = await fetch('http://localhost:8080/', { mode: 'cors' });
const data = await response.json();
console.log({ data });
} catch (error) {
console.error('CORS error:', error);
}
};
useEffect(() => {
makeAPICall();
}, []);
return (
<div className="App">
<h1>React CORS Guide</h1>
</div>
);
}
export default App;
🧠 ここで何が起きているのか?
-
Fetch APIを使用して
http://localhost:8080/にGETリクエストを送信するmakeAPICall関数を定義します。 -
{ mode: 'cors' }オプションは、これがクロスオリジンリクエストであることをブラウザに明示的に伝えます。 -
useEffectフックは、<App />コンポーネントがマウントされるとすぐにリクエストが実行されるようにします。
🔥 CORSエラーが発生することを想定する
Reactアプリを次のコマンドで実行すると:
npm start
そして、ブラウザの開発者コンソールを確認すると、次のようなCORSエラーが表示されるでしょう。
Access to fetch at 'http://localhost:8080/' from origin 'http://localhost:3000' has been blocked by CORS policy.
これはブラウザの同一オリジンポリシーがその役割を果たしているのです。つまり、異なるオリジンからのリソースへのアクセスをブロックしています。
CORSエラーを理解する
ブラウザコンソールで見たエラーは、典型的なCORSの問題です。クライアントとサーバーはどちらもlocalhostで実行されていますが、異なるポートにあります。Reactはlocalhost:3000、Expressはlocalhost:8080です。
つまり、同一オリジンポリシー(SOP)によれば、これらは異なるオリジンにあると見なされ、ブラウザはセキュリティ上の理由からリクエストをブロックします。
⚠️ 典型的なCORSエラーメッセージ
ブラウザがおそらく次のように通知したはずです。
Access to fetch at 'http://localhost:8080/' from origin 'http://localhost:3000'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is
present on the requested resource. If an opaque response serves your needs,
set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
これを分解して見てみましょう。
-
サーバーからのレスポンスに
Access-Control-Allow-Originヘッダーが見つからなかったため、ブラウザはレスポンスをブロックしています。 -
fetchモードを
'no-cors'に設定するという回避策も提案していますが、それでは操作できない限定的で不透明なレスポンスが返されるだけです。 -
そして重要なのは、クライアント側には何の問題もありません。CORSはクライアント側のエラーではなく、サーバーがどのように応答するかに基づくブラウザによって強制されるルールです。
✅ CORSは常にサーバー側で処理されるべき
ブラウザがアクセスを許可するかどうかを制御するのはサーバーのレスポンスですから、適切な解決策は、サーバーが正しいCORSヘッダーを送信するように設定することです。
開発中にプロキシを使用してクライアント側でCORSを回避することはできますが、クリーンで本番環境にも安全な方法は、サーバー側でCORSを直接有効にすることです。
🔧 ExpressサーバーでCORSを有効にする
Expressサーバーのapp.jsに戻り、/corsエンドポイントを更新しましょう。
app.get('/cors', (req, res) => {
res.set('Access-Control-Allow-Origin', '*');
res.send({ msg: 'This has CORS enabled 🎈' });
});
ここで何が起きているのか:
-
Access-Control-Allow-Originヘッダーが*に設定されています。これは、どのオリジンでもこのリソースにアクセスできることを意味します。 -
これはデモンストレーション目的でCORSを有効にする簡単で手早い方法です。(後で、アクセスを特定のオリジンのみに制限したくなるかもしれません。)
🔁 Reactクライアントを更新する
次に、Reactアプリに戻り、fetchのURLを新しいエンドポイントを対象とするように更新します。
const response = await fetch('http://localhost:8080/cors', { mode: 'cors' });
変更を保存し、必要に応じてReact開発サーバーを再起動します。
🧪 実際にテストしてみる
ブラウザを開いてアプリを更新してください。今回は、ブラウザがレスポンスを通過させ、コンソールに次のようにログが表示されるはずです。
{ data: { msg: 'This has CORS enabled 🎈' } }
成功です!CORSを正しく設定した状態で、最初のクロスオリジンリクエストを行えました。
修正の確認:CORSが機能する
サーバーでCORSヘッダーが正しく設定されると、エラーは消え、ReactアプリはレスポンスをJSONとして正常に受信します。🎉 すべて期待どおりに動作します。ただ、変更を反映させるにはバックエンドサーバーを再起動する必要があるかもしれないことを忘れないでください。
🎯 CORSを特定のオリジンに制限する
どのオリジンでもリソースにアクセスできるようにするワイルドカード(*)を使用する代わりに、CORSを特定のドメイン、たとえばReact開発サーバーに制限できます。
app.get('/cors', (req, res) => {
res.set('Access-Control-Allow-Origin', 'http://localhost:3000');
res.send({ msg: 'This has CORS enabled 🎈' });
});
これは、許可されていないWebサイトがAPIにアクセスするのを防ぐために、本番環境で推奨されます。
⚠️ サーバーを変更できない場合
サーバーを変更することはCORSを処理する最もクリーンで堅牢な方法ですが、それが常に可能とは限りません。多くの開発者は、認証、通知、メールサービスなどの第三者製APIを使用するときにこの問題に遭遇します。そのような場合、サーバーのCORSポリシーを変更することはできません。
それが起きたら、手詰まりなのでしょうか。それとも、そうではありませんか?
🧞♂️ 回避策:Reactでプロキシを使用する
React開発に特化した賢い回避策は次のとおりです。開発サーバーを経由してAPIリクエストをプロキシすることです。
プロキシを、クラスメートの点呼に応答するようなものと考えてください。アプリは、リクエストが別の送信元(つまりサーバーのオリジン)から来たかのように装い、ブラウザのSOPの制限を回避します。
✏️ プロキシの設定方法
-
Reactプロジェクトのルートにある
package.jsonファイルを開きます。 -
proxyフィールドを追加します。
{
...
"proxy": "http://localhost:8080"
}
-
React開発サーバー(
npm start)を再起動すると、以降すべてのリクエストが静かにバックエンド経由でルーティングされます。
たとえば/corsからデータを取得する場合:
const response = await fetch('/cors');
これにより、内部でhttp://localhost:8080/corsにプロキシされ、ブラウザからはリクエストが同一オリジンに見えます。
🧪 第三者製サービスを使用したい場合
問題ありません。そこへのプロキシも設定できます。
{
...
"proxy": "https://randomservice.com"
}
次の点に留意してください。
-
プロキシされるのは、非HTML(通常はAPI)のリクエストのみです。
-
リクエストの
Acceptヘッダーはtext/htmlではあってはなりません。 -
より複雑な設定(たとえば複数のプロキシ)には、http-proxy-middlewareを使用してカスタムのプロキシ動作を設定します。
🧹 まとめ:ReactにおけるCORSのベストプラクティス
CORSに関して覚えておくべきことは次のとおりです。
-
CORSは常にサーバーレベルで修正することです。それが最も信頼性が高く安全な解決策です。
-
開発中は、Reactのプロキシ設定により、サーバーを変更することなくCORSの問題を回避できます。
-
APIサーバーを管理していない場合は、プロバイダーに連絡するか、自分自身のプロキシサーバーを使用してください。
-
CORS UnblockのようなChrome拡張機能は一時的に機能するかもしれませんが、本番環境では決して使用すべきではありません。
-
動作を明示的に保つため、fetchリクエストで常に
{ mode: 'cors' }を指定してください。 -
ほとんどのブラウザベースの回避策は、デプロイすると機能しなくなることを理解してください。早期に本番環境を計画しましょう。