👏

CORSが、わかったようでわからない人のための記事

2024/03/26に公開

本記事の目的

単語の理解と、実装手順の把握で終わっているCORSを、理解したい。
単語の意味より、なぜそれが存在するのか、どういう背景で作られたのかを中心に理解を深めたい。

対象

CORSについて、意味はなんとなくわかったけど、いまいち理解がしっくり来ていない人
(=僕です)

CORSとは

CORSの字義的な説明は、省くとして、
重要な点は、
CORSは、同一オリジンポリシーを回避するための方法」であるということです。
あくまでも、同一オリジンポリシーというものがあり、その対処法としてCORSがあると言えます。
なので、同一オリジンポリシーから理解していく必要があります。

同一オリジンポリシー

では、同一オリジンポリシーとはなにか。
「異なるオリジンのリソースへのアクセスを制限するブラウザのセキュリティ
です。

重要なのは、「ブラウザのセキュリティ」である点です。

〇ブラウザ(ChromeやSafari)に用意された利用者が安全なWebブラウジングをするための仕組み
✕Web開発者が、自分のWebアプリを守るために用意した仕組み
であることが重要です。

具体的に説明します。

ブラウザは、javascriptを使うなどして、サーバーへリクエストを行います。
サーバーはリクエストに応じてレスポンスを返却しますが、
ブラウザは、
「このサーバーからのレスポンスは受け取っていいのか?」
をチェックして、問題がなければ、受け取っています。

ブラウザは同一オリジンポリシーに基づき、
サーバーのオリジンによってブラウザの対応が変わります。

  • 開いているページとオリジンが同じサーバーの場合

ブラウザくん:このサーバーは同じオリジンで安全だから、リクエストは受け取ろう!

  • 開いているページとオリジンが異なるサーバーの場合

ブラウザくん:このサーバーは今開いているページとオリジンが違うので、もしかしたら危険かもしれない。だからこのレスポンスは受け取っては捨てておこう。。[1]

と判断しています。

つまり、
ブラウザは、レスポンスを受け取っていいか? をチェックしているのであって、
リクエスト先のサーバーがレスポンスを返すかどうかとかは範疇外です。
そういう意味で、同一オリジンポリシーは「あくまでもブラウザのセキュリティ」なのです。

では、なぜ、安全なWebブラウジングには、同一オリジンポリシーが必要だったのでしょうか?

同一オリジンポリシーが適用されている理由

ブラウザに同一オリジンポリシーが適用されているのは、
それがないと、ブラウザ利用者の意図しない操作が行われてしまう可能性があるからです。

例えば、以下のようなケースを考えます。
・Aさん(普通のネット利用者)
・WAWASON.com(ログイン機能のあるECサイト※架空のサイトです。)
・偽WAWASON.com(WAWASONドットコムを模した詐欺サイト)

Aさんは、普段からWAWASONで買いものをしており、ブラウザでログインしています。
ある日、SNSで流れたきたリンクを開きました。
しかし、実はこのリンク、偽WAWASONに繋がるリンクでした。
その偽WAWASONのページは開くとjavascriptが実行され、本物のWAWASONに用意されたAPIにアクセスし、会員情報を取得しようとします。

同一オリジンポリシーがない場合

もちろん、WAWASONは会員認証を行っていますが、Aさんは普段からWAWASONを利用しており、
ブラウザのCookieにログイン認証が保存されているため、偽WAWASONはそれを利用して、本物のWAWASONのAPIにアクセスしてしまい、偽WAWASONはまんまとAさんの会員情報を盗みました。

同一オリジンポリシーがある場合

偽WAWASONが、ブラウザのCookie情報をもって、本物のWAWASONのAPIにアクセスしたところ、
ブラウザはこう考えます。

ブラウザくん:あれ?今開いているオリジン(偽WAWASONのオリジン)と、レスポンスが来ているオリジン(本物のWAWASONのオリジン)は異なっている。もしかしたら危険があるかもしれないから、同一オリジンポリシーに従って、このレスポンスは破棄しよう。[2]

こうして、うっかり偽WAWAZONにアクセスしてしまったAさんでしたが、
利用していたブラウザのおかげ(同一オリジンポリシーに基づくセキュリティのおかげ)で、
会員情報を盗まれずに済みました。

と、
いうことができるので、ブラウザには同一オリジンポリシーが適用されています。
他にも、iframeやWebstorageなどを使った攻撃からも守ってくれています。(詳細は割愛)

同一オリジンポリシーだと困るケース

しかし、同一オリジンポリシーでは困るケースがあります。

  • バックエンドとフロントエンドで、別のサーバーにデプロイしているので、オリジンが違う。
  • 自社の既存の別サービスと連携させたいが、オリジンが違う。
    など。

CORSを使った回避策

では、そのような場合、どうすればいいでしょうか。
そこで利用される、オリジン間でリソースを共有する方法をCORSと言います。

上述の問題を回避するには、サーバー側が、

サーバーさん:このオリジンは、私のオリジンとは異なるけど、ここだったらレスポンスを渡してくれていいよ。

と、ブラウザに常に教えてくれていればよさそうですよね。
そこで、
サーバー側から返すレスポンスに、そのことを明示するヘッダーをつけます。

Access-Control-Allow-Origin:['https::!"#$%&'(']

なんかがそうです。
この場合は、

サーバーさん:https::!"#$%&'(というオリジンは、私のオリジンとは異なるけど、ここだったらレスポンスを渡してくれていいよ

と、サーバーがブラウザに伝えていることになります。
比較してみましょう。

異なるオリジンの許可をしない場合

利用者(フロントエンドのプログラム):javascriptを実行して、サーバーからデータを取得しよう。ブラウザくんを介してリクエスト送信と。

サーバーさん:はい、レスポンス返しまーす。

ブラウザくん:レスポンス受け取りました。・・・。ん?このレスポンス今開いているページと違うぞ?怪しい可能性があるから、このレスポンスは渡せないな。破棄します。

利用者(フロントエンド):・・・なにも返ってこない。。(コンソールにエラー)

異なるオリジンの許可をした場合

利用者(フロントエンドのプログラム):javascriptを実行して、サーバーからデータを取得しよう。ブラウザくんを介してリクエスト送信と。

サーバーさん:はい、レスポンス返しまーす。あ、私のオリジンとは違うんですが、https::!"#$%&'(というドメインならレスポンス渡してもらっていいですよ。

ブラウザくん:レスポンス受け取りました。あ、なるほど。Access-Control-Allow-Origin:['https::!"#%&'(']とヘッダーにあるから、ここならレスポンスを渡してよいと。いま、開いているぺージは、https::!"#%&'(と同じオリジンだからレスポンスを渡します。

利用者(フロントエンド):データ返ってきた!

つまり、CORSとは

つまり、CORSの設定をすることは、

サーバーさん:はい、レスポンス返しまーす。あ、私のオリジンとは違うんですが、https::!"#$%&'(というドメインならレスポンス渡してもらっていいですよ。

この設定をすることであり、
「サーバー側で、自身のオリジンとは異なるオリジンでもレスポンスを返してもいい場合を定義すること」です。
オリジン以外にも、Methodを定義するなども可能です。

冒頭で、同一オリジンポリシーは、「ブラウザのセキュリティ」だと書きましたが、
CORSは、ブラウザに同一オリジンポリシーを回避してもらえるように、サーバー側で回避条件を指定するものとも言えます。
ブラウザのセキュリティだけど、対処をするのはサーバー側というのがややこしいポイントかもしれません。

CORSの範疇外なこと

ここまでで、CORSについて少し解像度が上がっていると幸いです。
最後に、CORSの範疇外なことを記載します。

前述のように、CORSの設定で、
サーバーが行っているのは、
「ブラウザに同一オリジンポリシーを回避してもらえるように、アシストをしている」
という程度です。

誤解してはいけないのは、これは
サーバーのセキュリティではない」ということです。
例えば、Laravelの場合、cors.phpで、

'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => false,

このようにして、CORSの設定が可能になっています。
僕は最初、

'allowed_origins' => [''],

にしたら、異なるオリジンからはまったくアクセスされないんだな!と思っていました。
これは、間違っています。
なにが違うかというと、

'allowed_origins' => [''],

としていても、ブラウザを伴わない、外部サービスからのリクエストの受信(Webhookなど)は可能ということです。
なので、CORSはあくまでもブラウザのセキュリティであり、サーバーを外部からのリクエストから守るものではなく、それは別の方法を考えるべきです。

まとめ

  • CORSは、同一オリジンポリシーを回避するための方法」である。
  • 同一オリジンポリシーは「異なるオリジンのリソースへのアクセスを制限するブラウザのセキュリティ」である。
  • ブラウザは、同一オリジンポリシーに基づき、レスポンスを受け取っていいか? をチェックしている(単純リクエストでない場合は、プレフライトリクエストを行い、リクエストを送っていいかをチェックしている。
  • CORSは、サーバー側で、同一オリジンポリシーの例外を設けることである。
  • CORSは、あくまでもブラウザのセキュリティであり、サーバーのセキュリティではない

参考文献

脚注
  1. 厳密には、単純リクエストでない場合は、プレフライトリクエストを行い、「このサーバーにこのリクエストをしていいのか」ということ自体をブラウザがチェックする。 ↩︎

  2. ブラウザとしては、どっちのオリジンが本物か偽物かは判別していないはずで、「オリジンが異なる」=「NG」と判別している。 ↩︎

Discussion