PlayにAjaxでCSRFトークンを送る方法

Play Framework(2.8)を使って、Ajaxでデータを送信する時の、CSRFトークンのエラーの解決方法を紹介します。
特定のルーティングの時だけCSRFトークンをOFFにするという方法ではなく、ちゃんとCSRFトークンを送信・受信・マッチする方法です。


## 0. 最初に

通常のTwirlを使ったフォームの作成・送信では、`CSRF.formField`を使います。
しかし、Ajaxの場合、このメソッドは使いません。
Ajaxの場合、`<input type="hidden" name="csrfToken" ...>`は含めずにフォームを送信します。

## 1. application.confの設定

まず、htmlのフォームにCSRFトークンを使わないので、cookieにCSRFトークンを登録させます。

`application.conf`に、`play.filters.csrf.cookie.name`の設定で、CSRFトークンのcookieの名前を設定します。

```scala:application.conf

play.filters.csrf.cookie.name = "csrf_token"

```

名前は、ここでは例として`csrf_token`としましたが、他のcookieとダブらなければ何でもいいです。

こうすることで、Playが自動的に、cookieにCSRFトークンを登録してくれるようになります。


## 2. Ajaxのヘッダ設定

JavaScriptでCookieから、`csrf_token`の値を取得して、Ajaxのヘッダに`Csrf-Token`というフィールド名で埋め込みます、


### axios

以下、axiosの場合の例です

```javascript

window.axios = require('axios');

// cookieから、application.confで指定したcookieのキーから値を取得
let csrfToken = document.cookie.match(new RegExp('(^|)' + 'csrf_token' + '=([^;]+)'))[2]

window.axios.defaults.headers.common = {
    "X-Requested-With": "XMLHttpRequest",
    "Csrf-Token": csrfToken
};

```

`"X-Requested-With": "XMLHttpRequest"`の部分も必要です。
CSRFトークンの値を入れるヘッダーのフィールドは`Csrf-Token`にする必要があります。

あとは、普通にaxiosのpostをするだけです。

```javascript
axios.post(url, {"foo": "bar"}) // .then ... catch ....
```

これで、AjaxにCSRFトークンをつけて、送信でき、Play側もそのCSRFトークンを認識してPOSTが受信されるようになります。


### jQuery

jQueryの場合は以下のような感じです。
axiosの例は動くことを確認していますが、このjQueryのコードは試してないので、動くかどうかは不明です。ただ、イメージとしてこんな感じですというものです。


```javascript

window.$ = require('jquery');

// cookieから、application.confで指定したcookieのキーから値を取得
let csrfToken = document.cookie.match(new RegExp('(^|)' + 'csrf_token' + '=([^;]+)'))[2]

$.ajaxSetup({
    // ...
    // ...
    headers: {
        "X-Requested-With": "XMLHttpRequest",
        "Csrf-Token": csrfToken
    },
    // ...
    // ...

})

```


## Cookieの扱い(CookieをJavaScriptで普段使わない方のために)

JavaScriptの場合、cookieの取得が少し面倒なので、プロトタイプ/クラスか、何かオブジェクトにまとめてしまうと楽です。

以下、クッキー操作のクラスの例です。

こちらのコードは https://stackoverflow.com/questions/10730362/get-cookie-by-name を参考にしています。

```javascript
export default class Cookie {
    static get(key) {
        const match = document.cookie.match(new RegExp('(^|)' + key + '=([^;]+)'));
        if (match) {
            return match[2];
        }
        return null;
    }

    static set (key, value) {
      document.cookie = key + '=' + value
    }
}

// 使用例

// cookie設定
Cookie.set("csrf_token", "1234567890abcdef......")

// cookie取得
let cookieValue = Cookie.get("csrf_token");

```

cookieは、文字列がただ`;`区切りでつながっているだけなので、

1. `split`で文字列を分割して配列にして、
2. `for`で一致するものを探す

という方法が、読む・書くという点では楽です。`get`メソッドに関しては、上記の例はループではなく、正規表現で一気に取得する例です。
`set`の方は`key`と`value`のバリデーションが入っていないので、チームで使う場合はもう少し慎重なコードを書いたほうがいいかもしれません。
ここではシンプルな例として書いてあるだけです。


## 終わりに

AjaxでのCSRFトークンの送信方法について、知っている人からしたら、どのライブラリやフレームワークを使っても、自然に思いつく方法なのかもしれません。
ただ、私はLaravelを主に使っていて、フレームワークがAjaxのCSRFトークンを自動的に処理していたので、あまり意識することがありませんでした。

Play Frameworkの場合、AjaxのCSRFトークンは自動的ではなく(そもそもJavaScript部分がフレームワークに含まれてない)、個別の設定が必要なため少しつまづきました。

さらに、Play(2.8)のドキュメントには、Twirlでの使用例しかなく、Ajaxの場合の例が書いてありません。
内容的には、「`X-Requested-With`と`Csrf-Token`というヘッダをAjaxでは使いましょう」ということが書いてあるくらいです。
ネットで検索しても、「Ajaxの場合はCSRFをOFFにさせる」方法の例が多く、根本的な解決方法の具体例も全然見つかりませんでした。

結局、Playのドキュメントを何度も読み、たまに「`cookie`からcsrfトークンを取得すればいいんだよ。Play + Angularの場合はそうやってるよ」というような感じの記事を見つけたので、そこからヒントを得て、PlayでAjax送信をする方法がわかりました（何度探しても、それらの記事を再度見つけることができなかったので、参照としてのリンクも貼れませんでした・・・）。