CORSがよくわからないので解説してみた＆Rails APIでのCORS設定

## はじめに
RailsでAPIを作っていますが、CORSの設定を行う必要がありました。そもそもCORSについてわかっていなかったので、CORSについてと、RailsでのCORS設定について説明していきます。

### このページで説明すること
- CORSとは
- オリジンとは、同一オリジンとは
- RailsでのCORS設定
- プリフライトリクエストとは

### バージョン
- Ruby 2.5.1
- Rails5
- APIモードでプロジェクト作成してます

## CORSとは？？
**Cross Origin Resource Sharing** の略。自分以外のどの``オリジン``からのCRUDリクエストを受け付けるか、受け付けないかをサーバーがフィルターし、セキュリティを高めるためのものです。

### もう少し詳しく言うと・・・

Webブラウザは私たちが知らない間に、HTMLや画像、フォントなどを取得するための多くのリクエストをサーバに送信しています。これらのリクエストはインターネットという大海原から大量に送信されてくるため、**これらのリクエストが怪しくないか、ハッキングしようとしていないか**をチェックせずにリソースを送り返すのはセキュリティ上大きなリスクとなりますよね。

そのため、Webブウラウザから送信されてくるリクエストが正当なものかどうかをチェックするための仕組み(セキュリティーポリシー)が存在しています。**「同一生成元ポリシー(Same-Origin Policy)」** です。

**「同一生成元ポリシー(Same-Origin Policy)」** を使うことで、異なる``オリジン``からのリソースへのアクセスに制限をかけることができます。あるページを開いたときに関連するリソースを、同じ``オリジン``からしか取得しない様にするのです。結果的に、**クロスサイトリクエストフォージェリ(CSRF)** などのセキュリティリスクを防ぐことができます。

**CORSはこの制限を一部解除し、異なる``オリジン``間でリソースを共有できるようにするための仕組みです。**

## オリジン(Origin)とは？

オリジンとは、URLの``スキーム・ホスト・ポート``の３つの組み合わせの事をいいます。

``http://www.exsample.com:80/index.html``というURLを例にすると、

- スキーム: ``http://``
- ホスト: ``www.exsample.com``
- ポート: ``80`` (ポート番号は省略可)

AとBという２つのオリジンがあったとして、**この3つがすべて一致したときのみ「AとBは同じオリジンである」**と言えます。

### 例えば・・・
オリジンA: ``http://example.com/app1/index.html``
オリジンB: ``http://example.com/app2/index.html``
➡️スキーム (http) とホスト (example.com) が同じなのでAとBは同一オリジンである

オリジンA: ``http://example.com/app1``
オリジンB: ``https://example.com/app2``
➡️スキームが異なるので、同じオリジンではない

オリジンA: ``http://example.com``
オリジンB: ``http://www.example.com``
オリジンC: ``http://myapp.example.com``
➡️ホストが異なるので、同じオリジンではない


### 同一オリジンである条件
長々と説明しましたが、「同一オリジンである」とは、以下の全てを満たす場合です。

✅ URLのホストが一致している
✅ スキーム（プロトコル）が一致している
✅ ポート番号が一致している

詳しくはMDNの「[Origin (オリジン)](https://developer.mozilla.org/ja/docs/Glossary/Origin)」を読んでみてください。

## Rails APIでのCORS設定
Rails APIは、``rack-cors``のgemを使って簡単にCORSの設定ができます。

- rack-corsのGithubページは「[こちら](https://github.com/cyu/rack-cors)」

APIモードでRailsアプリを作っている場合、既にGemfileに``rack-cors``が記載されていますが、デフォルトだとコメントアウトされています。コメントアウトを解除して``bundle install``します。

```ruby:Gemfile
gem 'rack-cors'
```

``bundle install``すると``config/initializers/cors.rb``が自動生成されるので、そのファイルを編集していきます。

### 基本設定
```ruby:config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins "*"

    resource "*",
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end
```
### 特定のオリジンのみ許可する
``origins "*"``は、どのオリジンからのリクエストも受け付ける設定です。特定のオリジンからのリクエストを許可したい場合は、下記の様に変更します。

**``localhost:3000``と``https://www.sample.com``からのリクエストを許可したい場合**

```ruby:config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'localhost:3000','https://www.sample.com'


    resource "*",
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end
```
### Cookieを利用する
Cookieを利用する場合は``credentials: true``を追記します。

```ruby:config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'localhost:3000'
    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head],
      credentials: true
  end
end
```

### Methods内の :options は何？?
methods内に記載されている:optionsは**プリフライトリクエスト**のために必要なので、消さない様にしましょう。

## プリフライトリクエストって？

CORSを用いると、異なるオリジンにPUT等のCRUDリクエストを送る際、**そのリクエストが許可されていることをサーバに一度確認してから**でないと、リクエストを送ることができなくなります。このリクエストのことを、``プリフライトリクエスト``と言います。

このリクエストのメソッドに``OPTIONS``が使用されます。そのため、``OPTIONSリクエスト``とも呼ばれます。

例えば、update(PUT/PATCH)リクエストでデータベース内のレコードをアップデートするとします。PUT/PATCHリクエストが送信される前に、まず``プリフライトリクエスト``がサーバに送信され、**送信元オリジンからのPUT/PATCHリクエストが有効かどうかをチェック**します。

有効でない場合はconsoleに``Response to preflight request doesn't pass access control check:``といったエラーメッセージが表示され、WebブラウザはCRUDリクエストの送信を断念します。

CORSを有効にするとこのプリフライトリクエストのために、Optionsメソッドが必要なので、methods内の:optionsを削除すると動かなくなります。注意しましょう。

## まとめ
以上、CORSの解説とRuby on Railsでの設定方法のご紹介でした。

## 宣伝： Railsアプリの作り方を学べる教材を公開しています!

Techpit様にて、Ruby on RailsとVue.jsでリアルタイムチャットアプリを作る教材を公開しています。
本記事で紹介したCORS設定はもちろんのこと、HTTP通信やAction Cableを使用したWebsocket通信についても詳しく説明しています。Rails APIモードでアプリケーションを作って見たい方はぜひ購入してください！

https://www.techpit.jp/courses/195

## 参考サイト
- [What is CORS?](https://www.codecademy.com/articles/what-is-cors)
- [OPTIONSメソッドが気になったのでCORSとプリフライトリクエストについて少し調べました](https://woshidan.hatenablog.com/entry/2018/11/26/215356)
- [CORSの動作を確認した](http://takapi86.hatenablog.com/entry/2017/09/18/172846)
- [domain と origin の違い](http://var.blog.jp/archives/58109649.html)
- [Handling CORS issues in your Rails API](https://medium.com/@Nicholson85/handling-cors-issues-in-your-rails-api-120dfbcb8a24)
- [RailsのAPIモードでいい感じにCORS設定をする](https://qiita.com/sugo/items/9c5f9cc5d88e6d7efa2d)