はじめに
私自身、以前SPAアプリケーションを作っている最中にオリジン間リソース共有 (CORS)に関するエラーにかなりはまってしまったので、関連用語を整理し、理解を深めるためにこの記事を書くことにしました。
以降の文章ではオリジン間リソース共有 (CORS)を省略してCORSと明記します。
オリジンとは
CORSの解説に入る前に、まずオリジンというものから解説します。
MDNでは以下のように定義されています。
ウェブコンテンツのオリジン (Origin) は、ウェブコンテンツにアクセスするために使われる URL の スキーム (プロトコル)、 ホスト (ドメイン)、 ポート番号 によって定義されます。スキーム、ホスト、ポート番号がすべて一致した場合のみ、 2 つのオブジェクトは同じオリジンであると言えます。
端的に言うと、URL内の「スキーム+FQDN(ホスト+ドメイン)+ポート番号」のことです。
上図だとhttps://www.aaa.com:443がオリジンということになります。
しかし一般的にポート番号は省略されるので、https://www.aaa.comまでをオリジンとして扱います。
次に、CORSと切っても切り離せない関係である同一オリジンポリシー(SOP)について解説します。
同一オリジンポリシー(SOP)とは
MDNでは
同一オリジンポリシーは重要なセキュリティの仕組みであり、あるオリジンによって読み込まれた文書やスクリプトが、他のオリジンにあるリソースにアクセスできる方法を制限するものです。
端的に言うと、同一オリジンポリシー(SOP)とは、『あるオリジンから取得したリソースから、別のオリジンのリソースへのアクセスを禁止するブラウザの機能』のことです。
- aからリソースAにアクセスした場合は取得に成功。
- aからリソースBにアクセスした場合は取得に失敗。
つまり、https://aaa.com/から取得したaページにおいて、同じオリジンであるhttps://aaa.com/AのリソースAは取得して画面に表示できるのに対し、異なるオリジンであるhttps://bbb.com/BのリソースBは取得できずに画面に表示されないということになります。これが同一オリジンポリシー(SOP)です。
ちなみに、SOPとは『Same-Origin Policy』の略になります。
いよいよ本題のCORSについて解説します。
CORSとは
MDN では
オリジン間リソース共有 (Cross-Origin Resource Sharing, CORS) は、追加の HTTP ヘッダーを使用して、あるオリジンで動作しているウェブアプリケーションに、異なるオリジンにある選択されたリソースへのアクセス権を与えるようブラウザーに指示するための仕組みです。
端的に言うと、CORSとは『同一オリジンポリシー(SOP)の制限を回避する際に利用する機能のこと。異なるオリジンからのアクセスを許可できる仕組み』です。
ちなみにCORSは『Cross-Origin Resource Sharing』の略になります。
通常、あるオリジン(下記参照http://localhost:3000)から、異なるオリジン(http://localhost:5000)に対してリソースを読み込もうとした場合、同一オリジンポリシー(SOP)が機能して以下のようなエラーが出力されます。
Access to XMLHttpRequest at 'http://localhost:5000' 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.
このエラーを回避して異なるオリジンからのリソースを読み込むには、バックエンド側でHTTPヘッダーの1つであるAccess-Control-Allow-Originをレスポンスに設定する必要があります。
Access-Control-Allow-Originとは、オリジン間リソース共有(CORS)の一部として使用されるHTTPヘッダーです。このヘッダーは、クライアントが許可されたオリジンからのリソースを受け取ることができるかどうかを示します。
具体例として、バックエンド側でAccess-Control-Allow-Originをレスポンスに設定したコードが下記になります。このコードNode.jsを使用しています(フレームワークはExpress)
const express = require('express');
const app = express();
// ミドルウェア設定
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
next();
});
上記のコードを記述することにより、クライアント(http://localhost:3000)が許可されたオリジン(http://localhost:5000)からのリソースをブラウザが拒否することなく受け取る事が可能となります。
またCORSは、シンプルリクエストとプリフライトリクエストの2つの異なるリクエストタイプを定義しています。
最初に、シンプルリクエストについて解説します。
シンプルリクエスト
シンプルリクエスト(単純リクエスト)とは、以下の条件をすべて満たすリクエストのことを指します。
1. メソッドは右記のいずれかである: GET、HEAD、POST
2. HTTPヘッダーは下記以外含まれていないか
- Accept
- Accept-Language
- Content-Language
- Content-Type(ただし、以下のいずれかの値に制限される: application/x-www-form-urlencoded、multipart/form-data、text/plain
MDNで定義されている シンプルリクエスト も合わせてご確認ください。
クライアントからFetchAPIなどを利用して送られたリクエストが、上記の要件を満たすシンプルリクエストだった場合は、サーバー側のレスポンスヘッダーに既述のAccess-Control-Allow-Originのみ設定すれば、クライアント(ブラウザ)がリソースにアクセスできるようなります。
しかし、上記のシンプルリクエストから少しでも条件が外れたリクエストの場合は、ブラウザはプリフライトリクエスト(後述)というものを送信します。
次に、プリフライトリクエストについて解説します。
プリフライトリクエスト
プリフライトリクエストはシンプルリクエストの条件を満たさない場合に行われ、リソースアクセスのための事前のアクセスを承認するためのもの、『お伺い』のようなものになります。
const express = require('express');
const app = express();
// ミドルウェア設定
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');
next();
});
- プリフライトリクエストはOPTIONSメソッドで行われ、Access-Control-Request-MethodヘッダーやAccess-Control-Request-Headersヘッダーなどの情報が含まれます。
- サーバーはプリフライトリクエストを受け取った後、Access-Control-Allow-OriginヘッダーやAccess-Control-Allow-Methodsヘッダー、Access-Control-Allow-Headersヘッダーなどを含むレスポンスを返します。
- ブラウザがプリフライトリクエストのレスポンスを受け取り、サーバーの設定が許可されたリクエストであることを確認した後、実際のリクエストを送信してリソースにアクセスします。
まとめ
私なりにまとめると、CORSは『同一オリジンポリシー(SOP)による制限がかかったままだと、別のオリジンからのfetchAPIなどのリクエストを受けれないので、それを緩和するためのブラウザに備わった機能』と言い換えられるのかなと思います。
またCORSは、シンプルリクエストとプリフライトリクエストの2つの異なるリクエストタイプを定義しており、それぞれサーバー側で適切なHTTPヘッダーを設定する必要があります。
おわりに
ここまで記事をご覧いただき、ありがとうございました。
この記事を見ていただいた方が、CORSについて理解が少しでも深まったとしたら嬉しいです。
間違いなどありましたらご指摘いただけると幸いです。
参考


