オリジン間リソース共有 (CORS)とは

## はじめに
私自身、以前SPAアプリケーションを作っている最中に`オリジン間リソース共有 (CORS)`に関するエラーにかなりはまってしまったので、関連用語を整理し、理解を深めるためにこの記事を書くことにしました。
以降の文章では`オリジン間リソース共有 (CORS)`を省略して`CORS`と明記します。

## オリジンとは
CORSの解説に入る前に、まず`オリジン`というものから解説します。
[MDN](https://developer.mozilla.org/ja/docs/Glossary/Origin "MDN")では以下のように定義されています。
> ウェブコンテンツのオリジン (Origin) は、ウェブコンテンツにアクセスするために使われる URL の スキーム （プロトコル）、 ホスト （ドメイン）、 ポート番号 によって定義されます。スキーム、ホスト、ポート番号がすべて一致した場合のみ、 2 つのオブジェクトは同じオリジンであると言えます。

端的に言うと、`URL内の「スキーム＋FQDN(ホスト+ドメイン)＋ポート番号」`のことです。

![スクリーンショット (1194).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1220815/7de34067-3ae3-36b0-8004-ad37ab5db589.png)

上図だと`https://www.aaa.com:443`がオリジンということになります。
しかし一般的にポート番号は省略されるので、`https://www.aaa.com`までをオリジンとして扱います。

次に、CORSと切っても切り離せない関係である`同一オリジンポリシー(SOP)`について解説します。
## 同一オリジンポリシー(SOP)とは
[MDN](https://developer.mozilla.org/ja/docs/Web/Security/Same-origin_policy "MDN")では
> 同一オリジンポリシーは重要なセキュリティの仕組みであり、あるオリジンによって読み込まれた文書やスクリプトが、他のオリジンにあるリソースにアクセスできる方法を制限するものです。

端的に言うと、同一オリジンポリシー(SOP)とは、`『あるオリジンから取得したリソースから、別のオリジンのリソースへのアクセスを禁止するブラウザの機能』`のことです。

![スクリーンショット (1193).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1220815/787974d7-080a-6b64-eb6e-a7c6e62f22bb.png)

* 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](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS "MDN") では
> オリジン間リソース共有 (Cross-Origin Resource Sharing, CORS) は、追加の HTTP ヘッダーを使用して、あるオリジンで動作しているウェブアプリケーションに、異なるオリジンにある選択されたリソースへのアクセス権を与えるようブラウザーに指示するための仕組みです。

端的に言うと、CORSとは`『同一オリジンポリシー(SOP)の制限を回避する際に利用する機能のこと。異なるオリジンからのアクセスを許可できる仕組み』`です。

ちなみにCORSは『Cross-Origin Resource Sharing』の略になります。

通常、あるオリジン(下記参照`http://localhost:3000`)から、異なるオリジン(`http://localhost:5000`)に対してリソースを読み込もうとした場合、同一オリジンポリシー(SOP)が機能して以下のようなエラーが出力されます。

```javascript: 
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ヘッダーです。このヘッダーは、クライアントが許可されたオリジンからのリソースを受け取ることができるかどうかを示します。

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin

<br>

具体例として、バックエンド側で`Access-Control-Allow-Origin`をレスポンスに設定したコードが下記になります。このコードNode.jsを使用しています(フレームワークはExpress)
```javascript: index.js(Node.js) 
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で定義されている [シンプルリクエスト](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS#%E5%8D%98%E7%B4%94%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88 "MDN：シンプルリクエスト") も合わせてご確認ください。

クライアントから`FetchAPI`などを利用して送られたリクエストが、上記の要件を満たす`シンプルリクエスト`だった場合は、サーバー側のレスポンスヘッダーに既述の`Access-Control-Allow-Origin`のみ設定すれば、クライアント（ブラウザ）がリソースにアクセスできるようなります。

しかし、上記の`シンプルリクエスト`から少しでも条件が外れたリクエストの場合は、ブラウザは`プリフライトリクエスト(後述)`というものを送信します。


次に、`プリフライトリクエスト`について解説します。

## プリフライトリクエスト
`プリフライトリクエスト`は`シンプルリクエスト`の条件を満たさない場合に行われ、リソースアクセスのための事前のアクセスを承認するためのもの、`『お伺い』`のようなものになります。

![スクリーンショット (1195).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1220815/9eee6edc-b5fe-ab10-9389-6dafbeaf2b6c.png)

```javascript: index.js(Node.js) 
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について理解が少しでも深まったとしたら嬉しいです。

間違いなどありましたらご指摘いただけると幸いです。

## 参考
https://developer.mozilla.org/ja/docs/Web/HTTP/CORS

https://qiita.com/att55/items/2154a8aad8bf1409db2b

https://shukapin.com/infographicIT/origin-policy