前提・実現したいこと
Ruby on Railsで転職用の個人アプリを制作しています。
Ajaxを使っていいね機能を実装中に以下のエラーメッセージが発生しました。
2回以上連続で「いいね」ボタンを押すと下記のエラーメッセージが表示されます。
(1回だけ押した場合は問題なく動きます)
実現したい挙動は、連続で「いいね」ボタンを押しても問題なく非同期通信が行われて、「いいね」→「いいねを取り消す」または「いいねを取り消す」→「いいね」と変更できるようにすることです。
間違っているかもしれませんが、非同期通信ではRailsがデフォルトでやってくれているCSRF対策の適用外となるから、自分でAuthenticityTokenを生成する記述をしなければならないのではないかと考え、
自分でAuthenticityTokenを生成する記述を他の記事を参考に書いてみましたが、同じメッセージが表示されてしまいました。
もしお分かりになる方いましたら、ご教示頂ければ幸いです。
発生している問題・エラーメッセージ
ActionController::InvalidAuthenticityToken
該当のソースコード
#####jQuery(like.js)
js
1$(function(){ 2 3 function buildHTML(like){ 4 var html = `<form class="button_to" method="post" action="/posts/${like.post_id}/likes/${like.id}"> 5 <input type="hidden" name="_method" value="delete"> 6 <input type="submit" value="いいねを取り消す"> 7 </form> 8 <div class="likeCounts"> 9 いいね数: 10 ${like.counts} 11 </div>` 12 return html; 13 } 14 15 function buildDeleteHTML(like){ 16 var html = `<form class="button_to" method="post" action="/posts/${like.post_id}/likes"> 17 <input type="hidden" name="_method" value="good"> 18 <input type="submit" value="いいね"> 19 </form> 20 <div class="likeCounts"> 21 いいね数: 22 ${like.counts} 23 </div>` 24 return html; 25 } 26 27 $('.button_to').on('submit', function(e) { 28 e.preventDefault(); 29 $.ajaxPrefilter(function(options, originalOptions, jqXHR) { 30 var token; 31 if (!options.crossDomain) { 32 token = $('meta[name="csrf-token"]').attr('content'); 33 if (token) { 34 return jqXHR.setRequestHeader('X-CSRF-Token', token); 35 } 36 } 37 }); 38 if($('.button_to').children().is('#like')) { 39 var formData = new FormData(this); 40 var url = $(this).attr('action') 41 $.ajax({ 42 url: url, 43 type: "POST", 44 data: formData, 45 dataType: 'json', 46 processData: false, 47 contentType: false 48 }) 49 .done(function(data){ 50 $('.button_to').remove(); 51 $('.likeCounts').remove(); 52 var html = buildHTML(data); 53 $('.like').append(html); 54 console.log('good'); 55 // $('.form__submit').prop('disabled', false); 56 }) 57 .fail(function(){ 58 alert('エラー'); 59 }) 60 } else { 61 var formData = new FormData(this); 62 console.log(this); 63 var url = $(this).attr('action') 64 $.ajax({ 65 url: url, 66 type: "DELETE", 67 data: formData, 68 dataType: 'json', 69 processData: false, 70 contentType: false 71 }) 72 .done(function(data){ 73 $('.button_to').remove(); 74 $('.likeCounts').remove(); 75 var html = buildDeleteHTML(data); 76 $('.like').append(html); 77 // $('.form__submit').prop('disabled', false); 78 }) 79 .fail(function(){ 80 alert('エラー'); 81 }) 82 } 83 }) 84})
#####ルーティング(routes.rb)
ruby
1Rails.application.routes.draw do 2(中略) 3 resources :posts do 4 resources :comments, only: :create 5 resources :likes, only: [:create, :destroy] 6 end 7(中略) 8end
#####コントローラー(likes_controller.rb)
ruby
1class LikesController < ApplicationController 2 def create 3 @like = current_user.likes.create(post_id: params[:post_id]) 4 if @like.save 5 @likeCounts = Like.where(post_id: params[:post_id]) 6 respond_to do |format| 7 format.json 8 end 9 else 10 flash[:alert] = 'エラーが発生しました。' 11 redirect_to post_path(@like.post.id) 12 end 13 end 14 15 def destroy 16 @like = Like.find_by(post_id: params[:post_id], user_id: current_user.id) 17 @like.destroy 18 if @like.destroy 19 @likeCounts = Like.where(post_id: params[:post_id]) 20 respond_to do |format| 21 format.json 22 end 23 else 24 flash[:alert] = 'エラーが発生しました。' 25 redirect_to post_path(@like.post.id) 26 end 27 end 28end
ビュー(show.html.haml)
ruby
1.like 2 - if user_signed_in? 3 - if current_user.already_liked?(@post) 4 .likeIcon 5 %i.far.fa-heart.likeIcon-fa-heart.heart 6 - else 7 .likeIcon 8 %i.far.fa-heart.likeIcon-fa-heart 9 - if current_user.already_liked?(@post) 10 = button_to 'いいねを取り消す', post_like_path(@post), method: :delete, id: 'delete' 11 - else 12 = button_to 'いいね', post_likes_path(@post), id: 'like' 13 .likeCounts 14 いいね数: 15 = @post.likes.count
#####JSON(create.json.jbuilder)
ruby
1json.user_id @like.user_id 2json.post_id @like.post_id 3json.id @like.id 4json.counts @likeCounts.length
#####JSON(destroy.json.jbuilder)
ruby
1json.user_id @like.user_id 2json.post_id @like.post_id 3json.id @like.id 4json.counts @likeCounts.length
調べてわかったこと
エラー原因を調べる過程で下記のことを知りました。
・application_controller.rbのprotect_from_forgeryメソッドを定義したことにより、Railsが自動的にCSRF対策をしてくれている。
・CSRFとは、Webアプリケーションに存在する脆弱性、もしくはその脆弱性を利用した攻撃方法のことで、掲示板や問い合わせフォームなどを処理するWebアプリケーションが、本来拒否すべき他サイトからのリクエストを受信し処理してしまう。その対策をする必要がある。
・フォーム送信前のページでワンタイムトークンが生成され、フォーム送信ページにhiddenで生成したトークンが埋め込まれる。リクエスト送信先でセッションに格納されたトークンとリクエストとして送られてきたトークンが一致するか確認する。この確認で一致していないというのが今回のエラー?
仮説
AuthenticityTokenが生成されていない?
リロードすれば、「いいね」ボタンを押して問題なく非同期で動くことから、2回目に「いいね」ボタンを押した時はapplication_controller.rbのprotect_from_forgeryメソッドが機能していないのではないかと予想した。
非同期通信ではRailsがデフォルトでやってくれているCSRF対策の適用外となるから、自分でAuthenticityTokenを生成する記述をしなければならない?
試したこと
下記のコードをajaxメソッドの前に追記したが、うまくいかなかった。
$.ajaxPrefilter(function(options, originalOptions, jqXHR) { var token; if (!options.crossDomain) { token = $('meta[name="csrf-token"]').attr('content'); if (token) { return jqXHR.setRequestHeader('X-CSRF-Token', token); } } });
補足情報(FW/ツールのバージョンなど)
Ruby 2.5.1
Rails 5.2.4.3
回答4件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/07/08 21:35