【概要編】CORS ～AWS100本ノック～ 14/100

# はじめに

みなさん、CORSって聞いたことありますか？
私自身、聞いたことはあるし、S3の設定をしたこともありますがいまいちわかってないままでした... :cry: 
この記事では、CORSについて説明していきたいと思います。
※サンプルを交えた説明は別記事で書こうと思っています

# CORSについて

## そもそも何の略？

`Cross-Origin Resource Sharing`の略で、日本語だと`オリジン間リソース共有`になります。
別のオリジン間でリソースを共有する、って正直よくわからない...そもそもオリジンって何だろう... :thinking: 

## オリジンとは？

オリジンは、以下の3つから構成されます。
- `スキーム(プロトコル)`
- `ポート番号`
- `ホスト(ドメイン)`

さぁ、突然ですが問題です。
以下の選択肢のうち、`http://example.com/dir/index.html`と同じオリジンになるのはどれでしょうか？
1. `http://example.com/dir2/new.html`
2. `http://example.com/dir/inner/other.html`
3. `https://example.com/page.html`
4. `http://example.com:81/dir/page.html`
5. `http://test.example.com/dir/page.html`

<details><summary>答えはこちら</summary>

1と2が正解です:smiley:

1. `http://example.com/dir2/new.html`👈ドメイン以下のパスが違いますが、同一オリジンです
2. `http://example.com/dir/inner/other.html`👈ドメイン以下のパスが違いますが、同一オリジンです
3. `https://example.com/page.html`👈プロトコルが違うので、別オリジンです
4. `http://example.com:81/dir/page.html`👈ポート番号が違うので、別オリジンです
5. `http://test.example.com/dir/page.html`👈ホストが違うので、別オリジンです

ドメインとオリジンは別物なので、ご注意を!!!
</details>

## Same-OriginPolicy(同一オリジンポリシー)

CORSの説明をする上で必ず出てくるのが、`同一オリジンポリシー`です。
これは、Webブラウザに搭載されている重要なセキュリティメカニズムで、異なるオリジンのリソースにアクセスすることを制限します。

![CORS-ページ6.drawio (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/f6418a02-39e3-aaa1-75e3-d34c6ac33e75.png)


### 同一オリジンポリシーの目的

そもそも異なるオリジンへのアクセスをなぜ制限する必要があるのでしょうか？
それは、`CSRF`などの脆弱性から個人情報などの大切なデータを守るためです。

### CSRF(クロスサイトリクエストフォージェリ)とは？

CSRFは、Webアプリケーションの脆弱性を利用してユーザが意図しない形で処理を実行するサイバー攻撃です。

流れとしては以下のようになります。
> 1-1. ユーザがECサイトにログインし、リクエストをします
> 1-2. ログイン状態などをチェックし、正しいリクエストなので問題なく受け付けます
> 2. 攻撃者が、偽のECサイトへ巧みに誘導するようなリンク付きのメールなどを送信します
> 3-1. それを受け取ったユーザは気づかずに、リンクをクリックします
> 3-2. 不正なリクエストが飛びますが、（もしサーバに脆弱性がある場合は）1-1でログインしているため受け付けてしまいます

これは非常に危険ですね:scream:
ただ、同一オリジンポリシーがあれば、リクエストをブロックしてくれて安心ですね


...と思いきや、そうでもありません。

![CORS-CSRF.drawio (5).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/3056fe30-2d6c-5ac6-139f-b20bb1d4dc20.png)


先ほどの図をもう一度見てみましょう！

異なるオリジンへのリクエストに関するレスポンスをブラウザがブロックしてくれるので、
読み取り操作(不正に個人情報を取得する、など)については効果がありそうです

しかし、図を見てわかる通りリクエストを送ることは出来てしまうので、サーバがリクエストを処理してしまうと不正にデータ登録などを行えてしまいます。
つまり、同一オリジンポリシーではセキュリティ的に不十分で、必ずサーバ側の対応が必要になるということです。

### 同一オリジンポリシーの制限の対象になるもの/ならないもの

全てのリクエストが同一オリジンポリシーの対象になるかというと、そうではありません。
以下のように制限の対象になるものと、対象にならないものがあります。

**制限の対象になるもの**

- JavaScriptでの非同期通信(XMLHttpRequestやFetch APIを使用した通信）
- Canvas
- Web Storage

**制限の対象にならないもの**
- `<script>`で読み込まれたJavaScript
- `<link>`で読み込まれたCSS
- `<img>`で表示された画像
- `<video>、<audio>`で再生されたメディアファイル
- `<object>、<embed>`で埋め込まれた外部リソース
- `@font-face`が適用されたフォント ※異なるオリジンは不可のブラウザもあり
- `<iframe>`でのコンテンツの読み込み　※レスポンスヘッダー`X-Frame-Options（非推奨）`にSAMEORIGINが設定されている場合、異なるオリジンは不可

## CORSがなぜ必要か

同一オリジンポリシーで、異なるオリジンへのアクセスを制限することはわかりました。
しかし、実際のWebアプリケーションで別オリジンにリクエストしないことの方が少ないと思います。

- 何かしら情報を取得したり、データ連携のために外部APIを利用する
- サブシステムにアクセスする
- S3にフロントエンドからファイルをアップロードする

こういったケースに対応するための`異なるオリジンにあるリソースへのアクセス権をブラウザーに与えるための仕組み`がCORSになります。

# CORSの仕組み

ここからCORSの仕組みについて説明していきます。
異なるオリジンへリクエストを送る場合、リクエストは`SimpleRequest(単純リクエスト)`と`PrefrightRequest`の2種類に分かれます。

それぞれの流れを図で表すと以下のようになります。
※図で出てくるヘッダーについては、後程説明します

## SimpleRequest
SimpleRequestは同一オリジンでのリクエストと同様に、サーバにリクエストを送ってレスポンスを受け取る形です。
もし、サーバー側で許可されていないOriginやメソッドの場合は、以下のようなCORSエラーになります。

```sh
Access to fetch at 'https://static-hosting-cors-server.s3.ap-northeast-1.amazonaws.com/preflight-correct.png' from origin 'http://static-hosting-cors-client.s3-website-ap-northeast-1.amazonaws.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.
```
SimpleRequestでCORSエラーの場合は、レスポンスのHTTPステータスとしては200になりました。
ブラウザの仕様なのか、S3側の問題ないのかここのはよくわからなかったので知ってる方はコメントで教えてください :bow: 

![CORS-シンプルリクエスト.drawio (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/fe08d0a8-c8ce-212f-d38e-57711cfaabda.png)

:::note warn
PrefrightRequestと違い、最初から本リクエストを送ってしまうため、CORS設定などに不備があると予期せぬ処理(データ更新)が実行されてしまいます。
ブラウザでレスポンスを見ることが出来ないからOKではない、ということです。

CORS設定は正しく行い、全てのOriginを許可する必要がある場合などは、必ず認証チェックなどの対策をするようにしましょう！
:::

## PrefrightRequest
PrefrightRequestが発生するリクエストの場合はまず、`OPTIONS`で同じ本リクエスト同じURLにアクセスします。
サーバー側で確認し問題がなければ200が返り、CORSエラーの場合は403が返ります。

PrefrightRequestのレスポンスで、ブラウザが本リクエストを送っても良いか判断し、本リクエストを送ります。この判断には後述の`Access-Control`ヘッダーが使われます。

![CORS-プリフライトリクエスト.drawio (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/a90b2123-d27e-7c9b-fac2-e26d672cd77f.png)

:::note info
OPTIONSメソッドは、サーバが許可しているHTTPメソッドの情報を取得できるメソッドです。
:::

## SimpleRequestとPrefrightRequest

さて、CORSのリクエストには2種類あるとわかりましたが、
どういう場合にSimpleRequestで、どういう場合にPrefrightRequestとなるのでしょうか？

- リクエストが[以下の1～4の全ての条件](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS#:~:text=%E5%8D%98%E7%B4%94%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%81%AF%E3%80%81%E4%BB%A5%E4%B8%8B%E3%81%AE%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E6%9D%A1%E4%BB%B6%E3%82%92%E6%BA%80%E3%81%9F%E3%81%99%E3%82%82%E3%81%AE%E3%81%A7%E3%81%99%E3%80%82)を満たす場合は、SimpleRequest
- それ以外の場合はPrefrightRequest

となります。

> １．HTTPメソッドが下記のいずれかである
> - GET
> - HEAD
> - POST
> 
> ２．ヘッダーに含まれるものが以下のみである
> - CORSセーフリクエストヘッダー(PrefrightRequestを必要としないヘッダー)
>   - Accept
>   - Accept-Language
>   - Content-Language
>   - Content-Type(以下のいずれか以外の場合は、SimpleRequestではない)
>     - application/x-www-form-urlencoded
>     - multipart/form-data
>     - text/plain
>   - Range
> - ユーザーエージェントによって自動的に設定されたヘッダー(上書きが出来ないヘッダー)
>   - Origin
>   - Connection
>   - Hostなどの[一部ヘッダー](https://developer.mozilla.org/ja/docs/Glossary/Forbidden_header_name) 
> 
> ３．リクエストに使用されるどの XMLHttpRequestUpload にもイベントリスナーが登録されていないこと
> 
> ４．リクエストにReadableStreamオブジェクトが使用されていない


## ヘッダーについて
次にCORSで利用されるリクエストヘッダー/レスポンスヘッダーについて説明します。

### リクエストヘッダー
#### Origin
リクエスト元のオリジンです。
クライアントが意識しなくても自動的に設定されます。また、この値は偽造することも出来ません。
`ex Origin:https://example.com`

#### Access-Control-Request-Headers
リクエストするカスタムヘッダーです。
SimpleRequestになるための条件で記載したヘッダー以外をリクエストする際に、自動的にこのヘッダーにも値が設定されます。
なので、SimpleRequesではこのヘッダーは送信されません。
`ex Access-Control-Request-Headers: Content-Type Authorization Hoge`

#### Access-Control-Request-Method:
リクエストするHTTPメソッドが設定されます。
なので、SimpleRequesではこのヘッダーは送信されます。
`ex Access-Control-Request-Method: GET, PUT`

### レスポンスヘッダー
#### Access-Control-Allow-Origin
アクセスを許可するオリジンを1つ以上指定します。許可リストにないオリジンからリクエストするとCORSエラーになります。
`*`と指定して全てを許可することも出来ますが、基本的には必要なオリジンにだけ絞ったほうが良いです。
`資格情報のあるCORSリクエスト`の場合は、`*`を指定するとエラーになります。
`ex Access-Control-Allow-Origin: https://example.com`

#### Access-Control-Allow-Headers
リクエストを許可するカスタムヘッダーを指定します。許可リストにないカスタムヘッダーをリクエストするとCORSエラーになります。
`資格情報のないリクエスト`の場合のみ`*`と指定して全てを許可することも出来ますが、基本的には必要なヘッダーにだけ絞ったほうが良いです。
`資格情報のあるCORSリクエスト`の場合は、`*`を指定するとエラーになります。
カスタムヘッダーの許可が不要な場合は、指定しなくても問題ありません
`ex Access-Control-Allow-Headers: Content-Type Authorization Hoge`

#### Access-Control-Allow-Methods
リクエストを許可するメソッドを1つ以上指定します。許可リストにないメソッドでリクエストするとCORSエラーになります。
`ex Access-Control-Allow-Methods: GET, PUT`

#### Access-Control-Expose-Headers
レスポンスとして公開するヘッダーを記載します。
ここで指定したヘッダー以外は、以下の`CORS セーフリストレスポンスヘッダー`を除いてクライアント(JavaScript)から扱うことが出来ません。
`ex Access-Control-Expose-Headers: Content-Type Authorization Hoge`
- Cache-Control
- Content-Language
- Content-Length
- Content-Type
- Expires
- Last-Modified
- Pragma

#### Access-Control-Allow-Credentials
異なるオリジン間リクエストに、資格情報(Cookie、認証ヘッダー、TLSクライアント証明書)を含めるか指定します。
含める場合は、`true`を設定します。
`資格情報のあるCORSリクエスト`の場合にtrueを設定しないとエラーになります。
`ex Access-Control-Allow-Credentials: true`

:::note warn
trueの場合は`Access-Control-Allow-Origin`と`Access-Control-Allow-Headers`に`*`を設定するとCORSのエラーになります。
また、`Access-Control-Expose-Headers`に関しては`*`を設定してもエラーにはなりませんが、レスポンスヘッダーがjsから取得できなくなります。
:::

#### Access-Control-Max-Age
PrefrightRequestの結果をキャッシュする秒数を指定します。
この時間内であれば、ブラウザはPrefrightRequestを送りません。
指定できる秒数はブラウザによって違うようです。
`ex Access-Control-Max-Age: 300`


# おわりに

今回はCORSについて概要や仕組みを説明しました。
少し記事が長くなってしまったので、実際に試してみる系は別記事で書きたいと思います :smile: