I had a working Rails5.2 application that needed a database restructure to improve the organisation of some tables. Since this change, I've found that some of my forms fails consistently on my local machine with this error:
ActionController::InvalidAuthenticityToken in PayBandsController#create
I've had to change a lot in the application as part of the refactor, of course, but the tests are all passing.
I can avoid the issue by setting skip_before_action :verify_authenticity_token
on the relevant controller (per previous SO threads) but this isn't a good fix, of course. I'd like to know what the root cause is and eliminate it.
render
in the HTML.skip_before_action
.This is all tested on my local machine with a valid session.
The form is created within a service object as the final line of a data table (so that I can add new lines to the table). Is this a potential cause of the problem - it's rendered in the service object rather than in the ERB? Although this approach worked fine before the refactor...
_table_data += ApplicationController.render(partial: 'datasets/pay_band_numbers_form', locals:{ _dataset_id: _dataset.id, _namespace: _key })
The partial is as follows and, as I said, works if placed on a page by itself (e.g. pay_bands/new) but not where it needs to be (on dataset/edit):
<%= form_for (PayBand.new), namespace: _namespace do |f| %>
<%= f.hidden_field :dataset_id, :value => _dataset_id %>
<%= f.text_field :label, :tabindex => 11, :required => true %>
<%= f.number_field :number_women, :tabindex => 12, :required => true %>
<%= f.number_field :number_men, :tabindex => 13, :required => true %>
<%= f.submit "✔".html_safe, :tabindex => 14, class: "button green-button checkmark" %>
<% end %>
The models involved are Dataset and PayBand, which are defined as follows.
class Dataset < ApplicationRecord
belongs_to :organisation
has_many :pay_bands, inverse_of: :dataset, dependent: :destroy
accepts_nested_attributes_for :pay_bands
default_scope -> { order(created_at: :desc) }
validates :name, presence: true
validates :organisation_id, presence: true
end
class PayBand < ApplicationRecord
belongs_to :dataset
has_many :ages_salaries_genders, inverse_of: :pay_band, dependent: :destroy
validates :dataset_id, presence: true
validates :label, presence: true
end
The relevant parts of the dataset controller are:
class DatasetsController < ApplicationController
protect_from_forgery with: :exception
load_and_authorize_resource
before_action :authenticate_user!
.
.
.
def edit
@dataset = Dataset.find(params[:id])
end
def update
@dataset = Dataset.find(params[:id])
if @dataset.update_attributes(dataset_params) && dataset_params[:name]
flash[:notice] = "Survey updated"
redirect_back fallback_location: dataset_path(@dataset)
else
flash[:alert] = "Attempted update failed"
redirect_to edit_dataset_path(@dataset)
end
end
.
.
.
private
def dataset_params
params.require(:dataset).permit(:name)
end
end
The pay-band controller is:
class PayBandsController < ApplicationController
protect_from_forgery with: :exception
# skip_before_action :verify_authenticity_token #DANGEROUS!!!!!
load_and_authorize_resource
before_action :authenticate_user!
def create
@pay_band = PayBand.new(pay_band_params)
if @pay_band.save
flash[:notice] = "Data saved!"
redirect_to edit_dataset_path(Dataset.find(@pay_band.dataset_id))
else
flash[:alert] = "There was an error. Your data was not saved."
redirect_back fallback_location: edit_dataset_path(Dataset.find(pay_band_params[:dataset_id]))
end
end
private
def pay_band_params
params.require(:pay_band).permit(:label, :number_women, :number_men, :avg_salary_women, :avg_salary_men, :dataset_id)
end
end
All of the above controller structure is identical to the working version of the app, before I fiddled with the data models!
===
Edit
I decided to check the headers, after some more research, so see if I could see the token value. However, the token value in the meta csrf-token
header tag doesn't match any of the 3 different tokens in the 3 different forms on this page. But one works anyway (the simple form that's just render
ed in the ERB template) and the other two don't (the ones render
ed in the service object). Which all leaves me just as confused as I was before!
Also, I tried removing Turbolinks from my GemFile and application.js file, re-running bundle
and rails s
, but it made no difference.
For future reference for anyone else having the same problem (AuthenticityToken errors for a form that's been built in a helper or service object), I moved the form directly into the view and render
ed it there - and the problem disappeared. I've removed the skip_before_action :verify_authenticity_token
and all is now well!