同一オリジンポリシー・CORSの脳内イメージを描く　～CORSエラーになるケースならないケース～


## 注意

* ざっくり脳内イメージを描くのに役立ったらうれしい。正確に理解したい方は別の文献にGO。
* 自身の同一オリジンポリシー・CORSの理解経緯・現状の脳内イメージ(メンタルモデル)の記録が目的です。
* この記事内の「API」はWebAPIとかRESTfulAPIとか呼ばれるようなやつを指します。
* 有害な理解があったら指摘いただけるとありがたいです。

## 要約
* フロントエンド(ブラウザ)からアクセスできるAPIは、基本的に自前のバックエンドのみ
* よそのAPIでも許可されていればアクセス可能
* 許可されてないAPIに対しては、そこにアクセスする自前のバックエンドを挟めばよい
* [メンタルモデル](#まとめメンタルモデル)はこちら

## ざっくり用語集
##### 同一オリジン
`http://example.net/index`と`http://example.net/users/5`
##### クロスオリジン
`http://example.net`と`http://example.org`
`http://example.net`と`httpｓ://example.net`

##### クロスオリジンへのアクセス
`http://example.net/index`内のJSが`http://example.org`にajax通信すること。
`http://example.net/index`内にあるリンクを踏んで遷移するのは該当しない。

##### 同一オリジンポリシー
Same Origin Policy/Same-Origin Policy。SOP、同一生成元ポリシーとも。
ブラウザはajax通信に関して、基本的に同一オリジンへのものしか許可しないということ。
CSRFとかXSSとかを防ぐために決まったらしい。

##### CORS
同一オリジンポリシーに例外を設ける仕組み。
特定の場合にクロスオリジンにアクセスしてよい。

##### CORSエラー
例外が設けられていないのにクロスオリジンにアクセスしようとしたエラー。
つまり同一オリジンポリシー違反。

## 理解の経緯

#### 前提・背景

前提としては普段はSpringみたいなフレームワークでJavaでMPAの業務システムを主に書いてる。
JSでDOM操作はするけどJSでのAPIアクセスやJavaでAPI作成は業務では基本的にない。

セキュリティ研修の教材で同一オリジンポリシーが出てきて調べたところ、
①自分のAPIアクセスのイメージが誤っていた
②具体性に欠ける説明が多かった
ため理解に時間がかかったので、メンタルモデルをまとめておく。

自分みたいな人向けに、実際に試せるHTMLになっています(APIの仕様変更がなければ)。

#### CORSエラーにならなかった例

APIはフロントエンドから自由にアクセスできると思ってたので同一オリジンポリシーが意外だった。
(言われてみるとどっかでCORSエラー踏んだことはあったはずなんだけど...)

例えば、↓の記事の内容であれば、自前でバックエンドを用意せずとも、

https://qiita.com/j-work/items/8f353db0d9bce5ff3b2a


```address.html
<html>
    <body>
        <button onclick="fetchData()">取得</button>

        <script>
            function fetchData() {
                const url = 'https://zip-cloud.appspot.com/api/search?zipcode=7830060'
                fetch(url) //fetchAPIのメソッドチェーン
                    .then((response) => response.json())
                    .then((data) => console.log(data))
            }
        </script>
    </body>
</html>
```
のようにフロント側で処理できるのでは？と思っていた。
（当該記事については内容を試したことがあるわけでなく、容易に使えるAPIを探していてたどり着いた）

そこで実際に上記コードを実施したところ、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1768084/cc33b4c6-5b2e-0e30-89d3-a7c382fc888c.png)

あれ、エラーにならない...🤔

#### CORSエラーになった例
エラーになるコードを求めて↓の記事に辿り着いた

https://qiita.com/Kohei-Sato-1221/items/0c64ebb9b3d3c38ff37b

```bitcoin.html
<html>
    <body>
        <button onclick="fetchData()">取得</button>

        <script>
            function fetchData() {
                const url = 'https://api.bitflyer.com/v1?product_code=BTC_JPY'
                fetch(url)
                    .then((response) => response.json())
                    .then((data) => console.log(data))
            }
        </script>
    </body>
</html>
```

試したところ、無事「Access to fetch at ～」というエラーになった。
うれしい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1768084/327a5508-8547-d9b3-81b4-940bb7349b2a.png)

#### 何が起きているか
クロスドメインへのアクセスはブラウザが許可しないらしいが、どうやって実現してるのか不明だった。
（一律アクセス禁止ではCORSが実現できないので）
`bitcoin.html`についてネットワークタブを確認したところ、ステータスコードが`404`だった。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1768084/48058128-bd3a-5217-bc99-443acfe17ab6.png)

ステータスコードが存在している≒アクセス自体は行っている感じがしたのでその観点で調べたところ、
[プリフライトリクエスト](https://developer.mozilla.org/ja/docs/Glossary/Preflight_request)と呼ばれる事前チェックをブラウザが自動的に実施していることが分かった。

その際にCORSの設定が適切にされていればチェックOKでリクエストが実行され、NGならエラーとなる。

比較のために`address.html`を確認したところ、
`access-control-allow-origin` `sec-fetch-mode: cors`などの記述がある。
この辺がポイントらしい(ざっくり理解)。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1768084/7d118120-3fb5-30fe-327b-514d8dac7bd7.png)


## まとめメンタルモデル
※ほんのりインデントしてるのはシーケンス図っぽさを出すためです

#### 自前のバックエンド通信
JS「バックエンドのデータ取得したいです」
　ブラウザ「同一オリジンだね。了解」
　ブラウザ「データをリクエストするよ」
　　バックエンドAPI「レスポンスですどうぞ」`status code: 200`
　ブラウザ「レスポンスきたよ」
JS「ありがとう！」

#### 郵便番号APIの場合
JS「郵便番号のデータ取得したいです」
　ブラウザ「クロスオリジンだね。一旦スタンバイして」
　ブラウザ「うち(ページ取得元オリジン)からアクセスしていい？」`プリフライトリクエスト`
　　郵便番号API「だれでもウェルカム」`status code: 200`
　ブラウザ「データをリクエストするよ」
　　郵便番号API「レスポンスですどうぞ」`status code: 200`
　ブラウザ「レスポンスきたよ」
JS「高知県」

#### ビットコインAPIの場合
JS「ビットコインのデータ取得したいです」
　ブラウザ「クロスドメインだね。一旦スタンバイして」
　ブラウザ「うち(ページ取得元オリジン)からアクセスしていい？」`プリフライトリクエスト`
　　ビットコインAPI「ダメです」`status code: 404`
　ブラウザ「リクエストは無かったことにするよ」
JS「あぁぁ...」`CORSエラー`

#### あるべきビットコインAPIの利用方法
JS「ビットコインのデータをバックエンドから取得したいです」
　ブラウザ「同一ドメインだね。了解」
　ブラウザ「データをリクエストするよ」
　　バックエンドAPI「データをリクエストするよ」
　　　ビットコインAPI「レスポンスですどうぞ」`status code: 200`
　　バックエンドAPI「レスポンスですどうぞ」`status code: 200`
　ブラウザ「レスポンスきたよ」
JS「ありがとう！」

元記事内にもあるけど自前のバックエンド経由でアクセスすればいいわけですね
~~バックエンド「外部APIアクセスか、私も同行する」
JS「ビットコ院」~~

## メモ
* ajaxではなんとなくJSが直接(？)サーバにアクセスするイメージだった。HTMLをレンダリングするように、ブラウザがリクエストを実行してるというメンタルモデルが必要だった。
* 「ブラウザが判断してる」は正しいんだけど、サーバ側の設定を読み取ってるので、広くとればサーバ側で許可を出してると言えるのかも。であれば原則全通しでサーバ側が必要に応じて拒否でもいいのかなと思ったけど、都度実装のコストとか実装漏れとか考えると今の仕組みはよくできてそう。
* URLの例を出すときは`example.com` `example.org` `example.net`が良いという知見を得た。
* IMEがいつまでもビットコインを正しく変換してくれなかった。