1
2

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-10-25

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の名前を設定します。

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の場合の例です


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をするだけです。

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

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

jQuery

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


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 を参考にしています。

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の方はkeyvalueのバリデーションが入っていないので、チームで使う場合はもう少し慎重なコードを書いたほうがいいかもしれません。
ここではシンプルな例として書いてあるだけです。

終わりに

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

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

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

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

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2