isomorphic-fetch(jQuery以外)を使用してRailsのCSRFトークン検証への対応方法

Reactのテストツールである[Jest](https://github.com/facebook/jest)の使い方を調べようと[こちらの記事](http://qiita.com/chimame/items/136d3a35b79e553a7f97)のソースに非同期処理を入れました。
その過程で(isomorphic)fetchを使用してのRailsのCSRF検証のパス方法を調べたのを記事にしておきます。

#前提条件
reduxのexampleである[Todos](http://redux.js.org/docs/basics/ExampleTodoList.html)のサーバAPIバージョンを元にしています。
てかこれでのAPI作成の記事ってGET処理はあるんですけど、POSTといった登録更新系の記事はほとんどなくないですか？
なのでReduxのTodosのAPIバージョンとしてソースを見てもいいと思います。

#使用環境
Ruby:2.3.1
Rails:5.0.0.1
Reactjs:15.3.2
isomorphic-fetch:2.2.1

#本記事の全体ソース
https://github.com/chimame/react_on_rails_template

#Rails側のAPIおよび解説
##ソース
詳しくはgithubのソースを見てもらえればいいと思いますが、特に難しいことはしていません。

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

```rb:app/controllers/todos_controller.rb
class TodosController < ApplicationController
  ・・・
  # POST /todos
  # POST /todos.json
  def create
    @todo = Todo.new(todo_params)

    if @todo.save
      render json: @todo, status: :created
    else
      render json: @todo.errors, status: :unprocessable_entity
    end
  end

  # PATCH/PUT /todos/1/toggle
  # PATCH/PUT /todos/1/toggle.json
  def toggle
    if @todo.update({completed: !@todo.completed})
      render json: @todo, status: :ok
    else
      render json: @todo.errors, status: :unprocessable_entity
    end
  end
  ・・・
end
```

ここでのポイントは**API（Ajax）処理をするにも関わらず、CSRFチェックを行うこと**です。jQuery(ujs)使うならあまり気にしない人もいるのではないしょうか。

##解説
注目してほしい点は`protect_from_forgery with: :exception`です。この指定をすることによってCSRFトークンのチェックが行われるようになります。（デフォルト）
[こちらの記事](http://shindolog.hatenablog.com/entry/2014/09/01/013840)にも書かれていますが、RailsのCSRFトークンのチェックは以下の判定がすべてfalseの場合にエラーとなります。

>設定ファイルで config.application_controller.allow_forgery_protection を false に設定しているか
>リクエストのHTTPメソッドがGETであるか
>リクエストのHTTPメソッドがHEADであるか
>セッション変数 _csrf_token の値とリクエストボディの authenticity_token の値を比較した結果、正しいと判断されるか
>セッション変数 _csrf_token の値とリクエストヘッダーの X-CSRF-Token の値を比較した結果、正しいと判断されるか

要は私のソースではCSRFのチェックが行われるということです。これは意図的にこのようにしております。

ちなみにRails5.0.0.1でのソースはこれです。

```rb: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
```

#Reactjs側のソースと解析
##ソース
```js:frontend/javascripts/containers/Sample/Todos.js
import {addTodo} from '../../utils/WebApi'

class Todos extends Component {
  ・・・
  render() {
    const { data, actions } = this.props
    return (
      <div>
        <TodoList todos={data} onTodoClick={actions.toggle} />
        <AddTodo onRegist={(text) => {actions.add(addTodo(text))}} />
      </div>
    )
  }
}
```

```rb:frontend/javascripts/components/TodoList/index.js
import {toggle} from '../../utils/WebApi'

const TodoList = ({ todos, onTodoClick }) => (
  <ul>
    {todos.map(todo =>
      <Todo
        key={todo.id}
        todo={todo}
        onClick={() => onTodoClick(toggle(todo.id))}
      />
    )}
  </ul>
)
```

```rb:frontend/javascripts/utils/WebAPI.js
import fetch from 'isomorphic-fetch'

const csrfToken = document.getElementsByName('csrf-token').item(0).content
const params = {
  method: 'POST',
  credentials: 'same-origin',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'X-CSRF-Token': csrfToken
  }
}
・・・
export const addTodo = (text) => {
  return fetch(`/todos`, {...params, ...{body: JSON.stringify({todo: {text: text}})}})
    .then(res => {
      if (res.status >= 400) {
        throw new Error("Bad response from server")
      }
      return res.json()
    })
    .then(payload => { return payload })
    .catch(error => { return undefined })
}

export const toggle = (id) => {
  return fetch(`/todos/${id}/toggle`, {...params, ...{method: 'PATCH'}})
    .then(res => {
      if (res.status >= 400) {
        throw new Error("Bad response from server")
      }
      return res.json()
    })
    .then(payload => { return payload })
    .catch(error => { return undefined })
}
```

##解説
大事なのはWebAPI.jsの`credentials: 'same-origin'`と`'X-CSRF-Token': csrfToken`部分です。
まず`'X-CSRF-Token': csrfToken`はRailsから出力されたcsrf-tokenをリクエストヘッダに入れております。もしjQueryを使っている人ならば同時にjQuery-ujsをほぼ使用しているでしょう。そのjQuery-ujsがこのリクエストヘッダにトークン入れる処理を行っているので普段は気にすることはないと思います。
しかし今回はjQueryのAjaxで通信するのではisomorphic-fetchで通信するため、自身で設定してやる必要があります。そこで上記のようにHTMLのmetaのcsrf_tokenを取得し、設定しています。
ただ、csrf-tokenを送るだけではRails側のチェックが通りません。
それは
>セッション変数 _csrf_token の値とリクエストボディの authenticity_token の値を比較した結果、正しいと判断されるか

と書いているようにサーバ側にてセッションを参照できる状態にしないといけません。Railsのセッションストアの設定によりますが、デフォルトでは`cookie_store`となっています。もしRedis等を使う場合でもcookieにはセッションIDを持っています。
サーバ側ではその受け取ったcookieでセッション変数を参照します。なので、クライアントからはcookieを送るのが必須なのです。なので`credentials: 'same-origin'`を記載して、同一ホストならばcookieを送るようにしています。

これでサーバ側のCSRFトークン検証をパスすることができるのです。
仮にReactjs側の通信をjQueryのAjaxを使用して通信すると検証をパスできることも確認できます。なぜならばRails側にてjQuery-ujsを出力しているからです。必要ならば確認してみてください。

次の記事は[Jest](https://github.com/facebook/jest)の予定でしたが少し足踏みをしてしまった感じです。**しかもRailsがAPIしか存在しないとなった場合はこの記事の内容は無用の長物となります。**
次はJestを…