ActionController::InvalidAuthenticityTokenの解消

こんにちは!oda@エンジニア1年目です!

最近、インゲージ入社前に個人的に作成したRailsアプリを、業務で学んだことを活かして作り替えるということをやっていました。

今日は、その中で発生したエラーについて書きたいと思います。

どんなエラーが発生したか

今回のRailsアプリは掲示板のようなものです。

投稿や投稿内容を削除する機能がありますが、非同期処理は行っておらず、実行するたびに画面の遷移が発生していました。

これを非同期処理にするというのが今回の修正内容です。

まずは処理内容がシンプルな削除機能の非同期化から着手したのですが、その時に次のエラーが発生しました。

Can't verify CSRF token authenticity.
Completed 422 Unprocessable Entity in 3ms (ActiveRecord: 0.0ms | Allocations: 468)

ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):

どんな修正をしたか

もともとは次のように削除ボタンを作成していました。

posts.html.erb

<%= button_to "削除する", post_path(@post), method: :delete %>

上記を次のように修正し、「削除する」ボタンがクリックされるとdeletePostが呼ばれるようにしました。

posts.html.erb

<button>削除する</button>

posts.js

function deletePost(post_id) {
  $.ajax({
    url: `/posts/${post_id}`,
    type: 'DELETE',
    dataType: 'json',
  })
  .done((data) => {
    console.log('削除しました');
  })
  .fail((data) => {
    console.log('削除に失敗しました');
  })
};

エラーを読む

Can't verify CSRF token authenticity.

CSRFトークンの信頼性が確認できないとのことです。

調べてみると、RailsではGET以外のリクエスト時には、authenticity_tokenを送信し、これがアプリケーションのビューの<head>タグ内にある<meta name="csrf-token" content="XXXXXXXXXXXX">と一致していることが確認できなければ、今回のようなエラーになるとのことでした。

実際に、開発者ツールを使ってリクエストを確認してみると、たしかに修正前のリクエストにはauthenticity_tokenが含まれていましたが、修正後のリクエストには含まれていませんでした。

button_toメソッドについて

もともと使用していたbuton_toメソッドから生成されるHTMLを確認すると、次のとおりでした。

<form class="button_to" method="post" action="/posts/(post_id)">
    <input type="hidden" name="_method" value="delete">
    <input type="submit" value="削除する">
    <input type="hidden" name="authenticity_token" value="XXXXXXXXXXXX">
</form>

これまで意識できていませんでしたが、buton_toメソッドは<form>タグを生成し、その中ににauthenticity_tokenを埋め込んでくれていました。

修正後は単なる<button>タグなので、authenticity_tokenは当然含まれていません。

このようにRailsがよしなにしてくれている部分については、問題にぶつかって調べてみて、初めて気が付くといことが多々あります。

試したこと

authenticity_tokenがリクエストに含まれていないのが問題であれば、入れてあげればよいのではないかということで、deletePostを次のとおり修正してみました。

function deletePost(post_id) {
    const token = $('meta[name="csrf-token"]').attr('content'); // 追加
  $.ajax({
    url: `/api/posts/${post_id}`,
        data: { authenticity_token: token }, // 追加
    type: 'DELETE',
    dataType: 'json',
  })
  .done((data) => {
    console.log('削除しました');
  })
  .fail((data) => {
    console.log('削除に失敗しました');
  })
};

これにより、無事削除ができるようになりました。

今後の課題

エラーの解消はできましたが、毎回、ajaxメソッドのdataauthenticity_tokenをわざわざ指定する方法がベストかというと疑問に感じています。

他にいい方法がないか調べているとRailsにはprotect_from_forgeryというメソッドがあり、今回のエラーと関連しているようなので、これが改善の糸口にならないかについては、今後調べてみようと思います。

まとめ

今回のエラー解決はベストな方法ではないかもしれませんが、自分で考えて試してみることで理解が深まりました。

今後もあれこれ試す中で成長していきたいと思います!