はじめに
私自身、以前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について理解が少しでも深まったとしたら嬉しいです。
間違いなどありましたらご指摘いただけると幸いです。
参考