スキマ時間で理解するCORS

## はじめに
本記事では、CORS（オリジン間リソース共有）とその周辺知識について簡単に解説します。
参考になった方はいいねをお願いします。

## オリジンとは
今回の主題でもあるCORSは、オリジン間リソース共有の略称です。
では、ここで言う **オリジン** とは何でしょう。

これは、**URLのスキーム（プロトコル）**、**ホスト（ドメイン）**、**ポート番号**の組み合わせのことを指します。


例えば、

```url
https://www.google.com:443
```
- スキーム（プロトコル）:https://
- ホスト（ドメイン）：www.google.com
- ポート：443

がオリジンの一例です。

:::note 
通常のURL（https）は、ポート番号（443）が省略されています。
（例）https://www.google.com ←ポート番号が省略されている
:::

よく「ドメイン」 と混同しがちですが、プロトコルとポート番号も含まれるので注意しましょう。


---
#### 練習
これは同一オリジン？

<!--ここから-->
**問1**
```
http://www.hoge.com:80
http://www.hoge.com:8080
```
<details>
<summary>答え</summary>
同一ドメインではない<br><br>
ポート番号が異なります。
</details>
<!--ここまで-->

---

<!--ここから-->
**問2**
```
https://www.hoge.com/foo.html
https://www.hoge.com/bar.html
```
<details>
<summary>答え</summary>
同一ドメイン<br><br>
ファイル名が異なりますが、「プロトコル」「ホスト」「ポート」が等しいことから同一ドメイン内のリソースと判断できます。
</details>
<!--ここまで-->

---

<!--ここから-->
**問3**
```
http://www.hoge.com
https://www.hoge.com
```
<details>
<summary>答え</summary>
同一ドメインではない<br><br>
プロトコルが異なります。<br>
※省略されているポート番号も違います。<br>
- http://www.hoge.com:80<br>
- https://www.hoge.com:443
</details>
<!--ここまで-->



## 同一オリジンポリシー とは
<strong>異なるオリジンからのリソースへのアクセスを制限する仕組み</strong>のことです。

Javascriptなどを用いた開発を行う中で、このようなエラーを見たことはありませんか？
```
Access to fetch at 'https://hoge.com/' from origin 'https://foo.com' 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
```
これは、異なるオリジンに対してリソースの取得を要求しているために起こるエラーです。


例えば
`https://foo.com/index.html`から`https://hoge.com/api`に対してリソースを要求する場合は、異なるオリジン間のやり取りなので上記のエラーが起こる可能性があります。

逆に、
`https://hoge.com/index.html`から`https://hoge.com/api`に対してリソースを要求する場合は、同一オリジン内のやり取りなのでエラーは起きません。

このように、Webブラウザにはオリジン間の安全なリソースのやり取りを行うためのセキュリティ上の仕組みが備わっています。
これを、<strong>同一オリジンポリシー</strong> や <strong>同一生成元ポリシー</strong> などと呼びます。

<br>

**実際にエラーを出してみる**
1. このサイト上で開発者ツールを開く（F12キーなどで開けます）
2. コンソールを開く
3. 以下のJSコードを実行する。 
```javascript
await fetch('https://google.com')
```
以下のようなエラーが出れば成功です。
```
Access to fetch at 'https://google.com/' from origin 'https://qiita.com' 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.
```
:::note warn
正確には、JSの非同期通信の場合リクエスト自体は拒否されず、それに対するレスポンスが破棄されています。
【参考】[同一オリジンポリシー - ウェブセキュリティ | MDN](https://developer.mozilla.org/ja/docs/Web/Security/Same-origin_policy)
:::
:::note
`await fetch('https://qiita.com')`
このサイト上で上記を実行した場合はどうなるでしょうか？
:::



### 制限の対象とならないもの
異なるオリジンへのアクセスでも、制限がかからない例外も存在します。

- `<script>`で読み込まれたJS
- `<link>`で読み込まれたCSS
- `<img>`で読み込まれた画像
- `<a>`で張られたリンク
- `<video>` および `<audio>` で再生されたメディア
- `<object>` または `<embed>` で埋め込まれた外部リソース
- `@font-face` が適用されたフォント。
- `<iframe>`関連

など。

【参考】[同一オリジンポリシー - ウェブセキュリティ | MDN](https://developer.mozilla.org/ja/docs/Web/Security/Same-origin_policy#%E7%95%B0%E3%81%AA%E3%82%8B%E3%82%AA%E3%83%AA%E3%82%B8%E3%83%B3%E3%81%B8%E3%81%AE%E3%83%8D%E3%83%83%E3%83%88%E3%83%AF%E3%83%BC%E3%82%AF%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9)



## CORS（Cross-Origin Resource Sharing） とは
CORSは、同一オリジン間リソース共有の略であり、**異なるオリジン間でリソースを共有するための仕様**のことを指します。

上述の通り、XMLHttpRequestやfetchなどで外部のオリジンへリクエストをした場合、同一オリジンポリシーに引っかかりリソースへのアクセスが制限されます。
これに対し、CORSに準じて適切にアクセス範囲を設定することで、任意のオリジンからのアクセスを許可することができます。

具体的な実装としては、リクエストを受ける側（サーバー側）のレスポンスに、リソースの共有を許可するためのヘッダーを追加します。

**実際に制限を解除してみる**

`https://hoge.com`から`https://foo.com`へリクエストする場合、

hoge.com側（クライアントサイド）
リクエストヘッダに`Origin:`フィールドを追加し自身のオリジンを記載する。
```http
Origin:https://hoge.com
```


foo.com側（サーバーサイド）
対応するレスポンスヘッダに`Access-Control-Allow-Origin`フィールドを追加し、アクセスを許可するオリジンを記載する。
```
Access-Control-Allow-Origin: https://hoge.com
```
:::note
`Access-Control-Allow-Origin:*`のような記載もできるが、セキュリティ上よろしくないのでやめましょう。
:::
【参考】[オリジン間リソース共有 (CORS)](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS)


### Simple Requests(単純リクエスト)
CORSのリクエストは主に2つに分けられ、その中でも`GET`,`POST`,`HEAD`などのシンプルなリクエストを<strong>単純リクエスト</strong>などと呼びます。

単純リクエストとは、以下の条件を全て満たしているものを指します。

- メソッドが以下のいずれかである
    - `GET`
    - `POST`
    - `HEAD`
    
- ヘッダーが以下の組み合わせで構成される（ユーザーエージェントによって自動的に設定されたヘッダー等を除く）
    - `Accept`
    - `Accept-Language`
    - `Content-Language`
    - `Content-Type`
    - `Range`
- Content-Type ヘッダーの値がいずれかである
    - `application/x-www-form-urlencoded`
    - `multipart/form-data`
    - `text/plain`


【参考】[オリジン間リソース共有 (CORS)|単純リクエスト](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:~:text=%E3%81%8C%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%99%E3%80%82-,%E5%8D%98%E7%B4%94%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88,-%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%81%AB%E3%82%88%E3%81%A3%E3%81%A6%E3%81%AF)




### Preflighted Requests(プリフライトリクエスト)
事前確認を行ってからリクエストを送る方式を<strong>プリフライトリクエスト</strong>と呼びます。

プリフライトリクエストでは、最初のリクエスト時にすぐリソースの要求をせず、`OPTIONS`メソッドを用いてアクセス可否の確認を事前に行います。

要するに、メインのリクエストを送る前に、「こんなリクエストしてもいい？」という事前確認を行います。

【参考】[オリジン間リソース共有 (CORS)|プリフライトリクエスト](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:~:text=%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%9B%E3%82%93%E3%80%82-,%E3%83%97%E3%83%AA%E3%83%95%E3%83%A9%E3%82%A4%E3%83%88%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88,-%E5%8D%98%E7%B4%94%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%81%A8)

#### なんでこんなめんどくさい処理があるの？
**A. 許可されているオリジンからのアクセスを担保 & サーバーがCORSに対応しているかの確認**

そもそも、単純リクエストとは元々Webブラウザで発行できていたクロスドメインのリクエストを指しています。（`form`経由のPOSTなど）
CORSが生まれる前から存在していたものであり、改めて特別な対応をする必要が無いため事前確認無しのリクエストを受け付けています。

対して、プリフライトリクエストは主にCORSによって発行可能になったリクエストであり、XMLHttpRequestなどを経由しないとブラウザから発行できないものなので、サーバーによっては対応していない場合があります。
そのため、事前に確認用のリクエストを送ることで、CORSに対応しているかと自身のドメインが許可されているかを確認しています。

**例）もしプリフライトリクエスト処理が無かったら...**

サーバー側で`Access-Control-Allow-Origin`の設定をしていない状態（許可設定をしていない）で、DELETEリクエストが送られると...
- ブラウザ側（クライアント側）
→　CORSのエラーになり、処理が失敗しているように見える。
- サーバー側
→　リクエスト自体は届いているので、削除処理が実行されてしまう。

といった現象が起こりえます。
事前確認無しでリクエストを送っているので、クライアント側とサーバー側とで結果の乖離が生じてしまいますよね、

プリフライトリクエストでは、このような問題を防ぐため、メインのリクエストの前に確認用のやり取りを行います。


:::note warn
副作用のあるメソッドには注意!!
**POST** は副作用があるが単純リクエストです。
でも、`Content-type:application/json`などが付くとプリフライトリクエストに...
:::


## 終わりに

今回はCORSについて簡単に解説をしました。
誰もが必ずつまづく部分だと思いますが、この記事を通してざっくりと理解をしていただけたら嬉しいです。

参考になった方は**いいね**をお願いいたします。