Cross-Origin Resource Sharing (CORS)

# Cross-Origin Resource Sharing (CORS)
## とは何か
Cross-Origin Resource Sharing (CORS)は、他のオリジンへのリクエスト（`XMLHttpRequest(XHR)`, `<img>` etc.）に対し、「どのオリジンに」「どういうリクエストを」許可するか指定することで、悪意あるサイトへ情報が漏洩するリスクから自サイトとそのユーザを保護できるようにした仕組みのことである。

Cross-Origin Resource Sharing (CORS)は、Webブラウザによって自動的に（一部は`XHR`/`Image`オブジェクトの設定値・プロパティ値に応じて適宜）設定される（アクセス許可を要求するための）リクエストヘッダと、サーバ側が（アクセスを許可する内容としての）応答として返すレスポンスヘッダとで構成される。

### Cross-Origin Resource Policy (CORP)との違い
Cross-Origin Resource Sharing (CORS)は、（クロスオリジンリクエストが[Cross-Origin Resource Policy (CORP)](https://qiita.com/toshi00ysm/items/d9f1db4b7b1a5cdbd169)によって制限されていないことを前提に）クロスオリジンリクエストに対して「どのオリジンから」「どのような」リクエストを許可するかを設定するもの。
一方[Cross-Origin Resource Policy (CORP)](https://qiita.com/toshi00ysm/items/d9f1db4b7b1a5cdbd169)は、もっとそれ以前の「そもそもクロスオリジンリクエストを許可するのかどうか」を制限する。
（つまり、`Cross-Origin Resource Policy`ヘッダに`same-site`/`same-origin`が設定されると、クロスオリジンリクエストそのものがまるごと禁止されてる状態。）

### プリフライト(preflight)リクエスト
Cross-Origin Resource Sharing (CORS)では、[プリフライト(preflight)リクエスト](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS#preflighted_requests)と言って、実際の`GET`や`POST`リクエストなどを送信する前に事前リクエストを（`OPTIONS`メソッドで）送信し、リクエスト先Webサーバに対してCORSの対応状況や許可ポリシーを事前確認するがある。
その事前確認のリクエストのことを「プリフライト(preflight)リクエスト」と呼ぶ。
プリフライトリクエストは、その必要がある場合にWebブラウザによって自動的に行われる。

プリフライトリクエストによって当該リクエストが許可されていない（ex: 自オリジンが`Access-Control-Allow-Origin`に含まれていない etc.）とわかった場合、Webブラウザは本来のリクエストの発行されないまま、リクエストは失敗する。

例えば以下のようなときに、事前にプリフライトリクエストが発行されている場合がある

- `GET`以外のメソッドでリクエストしたとき
- リクエストヘッダをカスタマイズしてリクエストしたとき

#### 参考
- [オリジン間リソース共有 (CORS) - HTTP | MDN #プリフライトリクエスト](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS#preflighted_requests)

### 設定方法
#### サーバサイド
サーバサイドから以下のHTTPレスポンスヘッダを必要に応じて設定する。

##### `Access-Control-Allow-Origin`
どのオリジンからのアクセスを許可するかを明示する。

```
Access-Control-Allow-Origin: <origin> | *
```

指定可能な値は以下（制限が厳しい（i.e. 安全な）順）

- `<origin>`: 許可するオリジン
- `*`（ワイルドカード）: 「全てのオリジンを許可する」ということ。なお、Cookieなどのクレデンシャル情報を含む場合は**使用できない**（＝ワイルドカードではなく、どのオリジンを許可するかを明示する必要がある）

###### 対となるリクエストヘッダ: `Origin`
どのオリジンからのリクエストかを表すリクエストヘッダで、Webブラウザによって自動的に付加される。

```
Origin: <origin>
```

サーバサイドで`Access-Control-Allow-Origin`の値をセットする際に、クライアントから送られた`Origin`ヘッダの値を見てセットするような実装を行う場合がある。

##### `Access-Control-Allow-Methods`
クロスオリジンリクエストに対し、許可するリクエストメソッドを明示・列挙する。

```
Access-Control-Allow-Methods: <method>[, <method>]*
```

指定可能な値は以下（制限が厳しい（i.e. 安全な）順）

- `<method>`: 許可するメソッド（コンマ区切り）
- `*`（ワイルドカード）: 「全てのオリジンを許可する」ということ。なお、Cookieなどのクレデンシャル情報を含む場合は**使用できない**（＝ワイルドカードではなく、どのメソッドを許可するかを明示する必要がある）

###### 対となるリクエストヘッダ: `Access-Control-Request-Method`
どのメソッドでのリクエストを行うのかを表すリクエストヘッダで、プリフライトリクエスト時にWebブラウザによって自動的に付加される。

```
Access-Control-Request-Method: <method>
```

##### `Access-Control-Allow-Headers`
クロスオリジンリクエストに対し、許可するリクエストヘッダを明示・列挙する。

```
Access-Control-Allow-Headers: <header-name>[, <header-name>]*
```

指定可能な値は以下（制限が厳しい（i.e. 安全な）順）

- `<header-name>`:  許可するリクエストヘッダ（コンマ区切り）
- `*`（ワイルドカード）: 「（`Authorization`ヘッダを除く）全てのリクエストヘッダを許可する」ということ。なお、Cookieなどのクレデンシャル情報を含む場合は特別な意味のない "*" というヘッダー名として扱われる。また、このワイルドカードを使っても`Authorization`だけはワイルドカードの対象には含まれず別途明示的に列挙する必要がある。

###### 対となるリクエストヘッダ: `Access-Control-Request-Headers`
どんなヘッダをリクエストに含めたいのかを表すリクエストヘッダで、プリフライトリクエスト時にWebブラウザによって自動的に付加される。

```
Origin: <origin>
```

##### `Access-Control-Expose-Headers`
クロスオリジンリクエストを発行したクライアントに対し、ヘッダ情報を開示するレスポンスヘッダを明示・列挙する。
ここに列挙されたレスポンスヘッダは、JavaScript側から値を参照することが出来る。

```
Access-Control-Expose-Headers: <header-name>[, <header-name>]*
```

指定可能な値は以下（制限が厳しい（i.e. 安全な）順）

- `<header-name>`:  許可するレスポンスヘッダ（コンマ区切り）
- `*`（ワイルドカード）: 「（`Authorization`ヘッダを除く）全てのレスポンスヘッダを許可する」ということ。なお、Cookieなどのクレデンシャル情報を含む場合は特別な意味のない "*" というヘッダー名として扱われる。また、このワイルドカードを使っても`Authorization`だけはワイルドカードの対象には含まれず別途明示的に列挙する必要がある。

##### CORS セーフリストレスポンスヘッダー
`Access-Control-Expose-Headers`に明示されていなくても、クライアントからアクセスされても安全とみなされるいくつかのヘッダがある。
これらを「[CORS セーフリストレスポンスヘッダー](https://developer.mozilla.org/ja/docs/Glossary/CORS-safelisted_response_header)」と呼ぶ。

CORS セーフリストレスポンスヘッダーは以下の通り。

- `Cache-Control`
- `Content-Language`
- `Content-Length`
- `Content-Type`
- `Expires`
- `Last-Modified`
- `Pragma`

##### `Access-Control-Allow-Credentials`
クロスオリジンリクエストに対し、送信されてきた（Cookieなどの）クレデンシャル情報を使ってレスポンスを生成することを許可しているかを表す。
クロスオリジンリクエストにおいてこのレスポンスヘッダが返ってこなかった場合、Webブラウザは当該リクエストのレスポンスを無視する（JavaScript側からレスポンスが見れなくなる）。

```
Access-Control-Allow-Credentials: true
```

##### Access-Control-Max-Age
クロスオリジンリクエストに対し、プリフライトリクエストのレスポンスのキャッシュ期限を指定する。

```
Access-Control-Max-Age: <delta-seconds>
```

`<delta-seconds>`: キャッシュ期限（秒）


#### クライアントサイド
クライアントサイドにおいて、CORSを意識する場面として以下がある。

##### `XMLHttpRequest`の`withCredentials`プロパティ
`XMLHttpRequest`（`XHR`; いわゆる`Ajax`）において、CookieなどのクレデンシャルをもちいてCORSを行う場合、`XMLHttpRequest`の`withCredentials`プロパティに`true`を設定する必要がある。

```js
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://example.com/', true);
xhr.withCredentials = true;
xhr.send(null);
```

これを設定しないと、ブラウザはCORSリクエストにCookieを付加**してくれない**。

###### 参考
- [XMLHttpRequest.withCredentials - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials)

##### `Fetch API`の`mode: 'cors'`オプションと`credentials`オプション
`Fetch API`でCORSリクエストを正常に行うには、`mode`オプションに`cors`を指定する必要がある。
また、それにCookieなどのクレデンシャルを含める場合は、`credentials`に`include`を指定する。

```js
// Fetch APIでCookieを使ったCORSリクエストを発行
const response = fetch('https://example.com', {
  mode: 'cors',
  credentials: 'include'
});
```

###### 参考
- [Fetch の使用 - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetch)
- [WindowOrWorkerGlobalScope.fetch() - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/WindowOrWorkerGlobalScope/fetch)
- [Fetch Standard #example-cors-with-credentials](https://fetch.spec.whatwg.org/#example-cors-with-credentials)

##### `<img>``crossorigin`属性、および`Image`のcrossOrigin`プロパティ
これを設定することで当該HTML要素が発行するリクエストを、CORSリクエスト化出来る。

Cookieなどのクレデンシャルを含める場合は`use-credentials`を、含めない場合は`anonymous`を設定する。
なお、空文字や認識できない文字は`anonymous`扱いされる。

```html
<!-- クレデンシャルを含める場合 -->
<img src="https://example.com" crossorigin="use-credentials">
<script>
  // 上記HTMLと同じ
  const img = new Image()
  img.src = 'https://example.com'
  img.crossOrigin = 'use-credentials'
</script>

<!-- クレデンシャルを含めない場合 -->
<img src="https://example.com" crossorigin>
<img src="https://example.com" crossorigin="">
<img src="https://example.com" crossorigin="anonymous">
<script>
  // 上記HTMLと同じ
  const img = new Image()
  img.src = 'https://example.com'
  img.crossOrigin = 'anonymous'
  // or img.crossOrigin = ''
</script>
```

なお、Canvasで別オリジンの画像を描画した際の具体的な影響としては、  
CORS設定がないと[`toDataURL()`](https://developer.mozilla.org/ja/docs/Web/API/HTMLCanvasElement/toDataURL)メソッドなどで画像データを取り出そうとしたときに`SecurityError`で失敗する（i.e. データを取り出さず描画するだけなら問題ない）  
などである。

###### CORS×画像×Canvasで時々起こったりするハマりポイントと対処法
Webブラウザには、過去のリクエストをキャッシュによって描画を高速化する機能がある。そしてキャッシュはその時のレスポンスデータだけでなくどのようなリクエストヘッダ・レスポンスヘッダが送受信されたかも保存されている。
これが時として、「CORSで画像をリクエストしたにもかかわらず、過去にCORSでない方法で取得した画像データがキャッシュによって返される」という事象が起こることがある。
そして前述の通り、CORS上不完全な設定で取得した画像はCanvasで`drawImage()`で画像編集するなどのあと[`toDataURL()`](https://developer.mozilla.org/ja/docs/Web/API/HTMLCanvasElement/toDataURL)メソッドなどで画像データを取り出そうとしたときに`SecurityError`で失敗する。
これによる不測の不具合発生を防ぐには、画像のリクエストURLに[キャッシュバスター](https://www.google.com/search?q=%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%A5%E3%83%90%E3%82%B9%E3%82%BF%E3%83%BC)を付加するなどでキャッシュを回避する手法が有効。

###### 参考
- [<img>: 画像埋め込み要素 - HTML: HyperText Markup Language | MDN #attr-crossorigin](https://developer.mozilla.org/ja/docs/Web/HTML/Element/Img#attr-crossorigin)
- [HTML crossorigin 属性 - HTML: HyperText Markup Language | MDN](https://developer.mozilla.org/ja/docs/Web/HTML/Attributes/crossorigin)

## 参照
- [オリジン間リソース共有 (CORS) - HTTP | MDN](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS)