Rails request_forgery_protection に関するメモ

production 環境を local に構築、動作確認を試みたとき、csrf で引っかかった。
その時の備忘。
ポイントは `action_controller` の `request_forgery_protection.rb` の `verified_request?` メソッド。

```actionpack-5.0.3/lib/action_controller/metal/request_forgery_protection.rb
      # Returns true or false if a request is verified. Checks:
      #
      # * Is it a GET or HEAD request?  Gets should be safe and idempotent
      # * Does the form_authenticity_token match the given token value from the params?
      # * Does the X-CSRF-Token header match the form_authenticity_token
      def verified_request?
        !protect_against_forgery? || request.get? || request.head? ||
          (valid_request_origin? && any_authenticity_token_valid?)
      end
```
4つのメソッドを評価し、true/false を判定する。
先頭の3メソッドは、configやhttp method の判定なので、基本的には問題にならない。
ポイントは、後半の2つ。

### valid_request_origin?
request.origin と　request.base_url が等価であることを評価している。

```actionpack-5.0.3/lib/action_controller/metal/request_forgery_protection.rb 
      # Checks if the request originated from the same origin by looking at the
      # Origin header.
      def valid_request_origin?
        if forgery_protection_origin_check
          # We accept blank origin headers because some user agents don't send it.
          request.origin.nil? || request.origin == request.base_url
        else
          true
        end
      end
```
前段に nginx を配置する場合、request の header に `$scheme`をリレーする必要がある。

``` .conf
    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP  $remote_addr;
        proxy_set_header Client-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;  # この行
        proxy_pass http://localhost:3000;
    }
```
そして、前段に aws ELB が配置される場合は、以下`$http_x_forwarded_proto` をリレーする。

``` .conf
  location / {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP  $remote_addr;
    proxy_set_header Client-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;　　# この行
    proxy_pass http://localhost:3000;
  }
}
```


### any_authenticity_token_valid?
ここで、具体的にPOSTに付帯する token を評価する。

``` .rb
      # Checks if any of the authenticity tokens from the request are valid.
      def any_authenticity_token_valid?
        request_authenticity_tokens.any? do |token|
          valid_authenticity_token?(session, token)
        end
      end

      # Possible authenticity tokens sent in the request.
      def request_authenticity_tokens
        [form_authenticity_param, request.x_csrf_token]
      end
```
formのparams と header をそれぞれ評価する。

具体的な評価は、

- compare_with_real_token(csrf_token, session)
- valid_per_form_csrf_token?(csrf_token, session)

で実施する。

### compare_with_real_token
session[:_csrf_token]にtokenを保管し、これと比較評価している。

```.rb
      def compare_with_real_token(token, session)
        ActiveSupport::SecurityUtils.secure_compare(token, real_csrf_token(session))
      end

      def real_csrf_token(session)
        session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
        Base64.strict_decode64(session[:_csrf_token])
      end
```
production環境のsession_storeで `secure` を指定していると、httpで動作させた場合にsessionにsotreされなくなるので注意。

```config/initializers/session_store.rb
  Rails.application.config.session_store :cookie_store, key: '_truckers_session', secure: Rails.env.production?
```