Rails上のAPIセキュリティについてメモ【初学者による調査】

## 導入

人生で初めてAPI設計におけるセキュリティについて考えましたので、最終的な実装内容にも簡単に触れながら考えを整理します。

### 各種環境について

サーバー・クライアント関係はAPI側がRails、クライアント側がReact（axiosによる送信）となる。
Rails側のエンドポイント設計はgrapeというgemによるもの。

サーバ： `localhost:3000`
クライアント: `localhost:3001`

マシンはMacBook Pro (13インチ, M1, 2020)を使用。

```console:Railsプロジェクト
% ruby -v
ruby 3.2.1
% rails -v
Rails 7.0.4.2
```

grapeのバージョンは1.7.0。

React側は `react@18.2.0` 、 `axios@1.3.4` 。

## 実装手順と説明

### protect_from_forgeryはデフォルトのまま

```ruby:app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  ...
```

rails newの時から触っていません。

これを「何かしらエラーが起きてしまう」からと `protect_from_forgery with: :null_session` にしてしまうと、アプリケーション全体でcsrf-tokenがなくともリクエストを通すようになります（で、認識が合っているかと…）。

検証そのものを行わなくするには `config.action_controller.allow_forgery_protection = false`　というように設定するそうです。

### forgery_protection_origin_checkの設定

```ruby:config/application.rb
module App
  class Application < Rails::Application
    # ~ 中略 ~
    config.action_controller.forgery_protection_origin_check = false
  end
end
```

https://railsguides.jp/configuring.html#config-action-controller-forgery-protection-origin-check

> CSRFの追加対策として、HTTPのOriginヘッダーがサイトのoriginと一致することをチェックすべきかどうかを設定します。

今回はrack-corsでオリジン関係の設定を行うため、Rails側の設定を `false` にします。

### rack-corsの導入

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

インストールを行い、設定を書きます。

```ruby:config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'localhost:3001'
    resource '*',
              headers: :any,
              methods: [:get, :post],
              expose: ['X-CSRF-Token']
  end
end
```

今回はトークンをヘッダーに仕込みます。

### トークンの受け渡しについて

#### サーバ上のユーザ認証、トークン生成時

クライアント側の情報を元にユーザ認証が成功したとします。
grapeのドキュメントにもある方法で、レスポンスヘッダーにトークンを乗せます。

```ruby:grapeによるAPI設計時に記述
post :endpoint_name do
  # 処理が成功
  header 'X-CSRF-Token', token
  # body内容の記述
end
```

#### クライアント側によるトークンの受け取り

参考サイトを参考に、トークンをaxios上の設定として保持します。

```react:axiosによるリクエスト成功時の処理
.then(response => {
  axios.defaults.headers.common['X-CSRF-Token'] = response.headers['x-csrf-token']
```

以後、リクエスト発行時にヘッダーを毎回設定します。


#### サーバ上のリクエスト処理

リクエストを受け取る際に、Rails上での参照は下記で成功しました。

```ruby:grapeによるAPI設計時に記述
env['X-CSRF-Token']
```

ちなみに今回はトークン参照後、サーバ側の自作メソッドでトークンを検証するようにしています。

## セキュリティ上の要件を検証

### セルフチェック

https://qiita.com/netebakari/items/41baa7e1d0b8d89f9d12

こちらの記事で、CORS設定を行う上でのCSRF対策要件として掲げている、

- [x] 重要な操作を行う場合は X-CSRFToken のようなヘッダを付けてリクエスト送信を行うような仕様にする

- [x] このヘッダのチェックはサーバー側でも正しく行う

- [x] サーバーでは正しく Access-Control-Allow-Origin ヘッダを出力する

はオールクリアしているはずかと思います。

### 暗号化に対する認識

~~手元に有用な参考文献がなくて恥ずかしいのですが、HTTPヘッダーはTLS通信時に暗号化される、という認識で合っていると思っています。~~

~~ですので、現在はブラウザ上にて簡単にトークンを確認できてしまいますが、追々HTTPSへ対応する際にこの問題は解消されるはずです。~~

追記： 暗号化されないそうです…。あくまでもHTTPSだと暗号化された通信路が開設されるというだけですね。

### セキュリティ面における本実装の不安

より安全なセキュリティ要件に基づいて設計を行う場合、諸々についてはcookieによる受け渡しで行うのが良いかと思います。

というのも、今回行っているaxiosの設定情報の活用について、それがどれだけ安全なものなのかが分からなかったです。もし今の状態では、攻撃者がJavascriptによってトークンを簡単に奪えてしまうとしたら、やはりHTTPSを前提にcookieによる受け渡しによって設計を行うと安全そうです。

メモ代わりに補足しますが、cookieを用いた設計へと変更する場合、

1. HTTPS対応以降、cookieのSameSite属性をNone、Secure属性にチェックを入れる
2. CORS設定上で `credentials` をtrueにする
3. Rails上にてレスポンス内容に `cookie[:token]` と書いて、set-cookieを行う

となるかと思われます。

トークンの保存場所としてcookieを使うべきか否か、という議論は今回は不問とさせてください。

## 参考サイト

実装はこちらのコードをおおいに参考しました。

https://midorimici.com/posts/rails-api-csrf