ActionController::InvalidAuthenticityToken in RegistrationsController#create
Per the comments in the core application_controller.rb
, set protect_from_forgery
to the following:
protect_from_forgery with: :null_session
Alternatively, per the docs, simply declaring protect_from_forgery
without a :with
argument will utilize :null_session
by default:
protect_from_forgery # Same as above
UPDATE:
This seems to be a documented bug in the behavior of Devise. The author of Devise suggests disabling protect_from_forgery
on the particular controller action that's raising this exception:
# app/controllers/users/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
skip_before_filter :verify_authenticity_token, :only => :create
end
You have forgot to add <%= csrf_meta_tags %>
in side your layout file.
e.g.:
<!DOCTYPE html>
<html>
<head>
<title>Sample</title>
<%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %>
<%= javascript_include_tag "application", "data-turbolinks-track" => true %>
<%= csrf_meta_tags %>
</head>
<body>
<%= yield %>
</body>
</html>
TLDR: You are probably seeing this issue because your form submits via XHR.
Few things first:
- Rails includes a CSRF token inside the head tag of your page.
- Rails evaluates this CSRF token anytime you perform a POST, PATCH or DELETE request.
- This token expires when you sign in or sign out
A bog standard HTTP sign-in will cause a full page refresh, and the old CSRF token will be flushed and replaced with the brand new one that Rails creates when you sign in.
An AJAX sign in will not refresh the page, so the crusty old, stale CSRF token, which is now invalid, is still present on your page.
The solution is to update the CSRF token inside your HEAD tag manually after AJAX sign in.
Some steps that I have shamelessly borrowed from a helpful thread on this matter.
Step 1: Add the new CSRF-token to the response headers which are sent after a successful sign in
class SessionsController < Devise::SessionsController
after_action :set_csrf_headers, only: :create
# ...
protected
def set_csrf_headers
if request.xhr?
# Add the newly created csrf token to the page headers
# These values are sent on 1 request only
response.headers['X-CSRF-Token'] = "#{form_authenticity_token}"
response.headers['X-CSRF-Param'] = "#{request_forgery_protection_token}"
end
end
end
Step2: Use jQuery to update the page with the new values when the ajaxComplete
event fires:
$(document).on("ajaxComplete", function(event, xhr, settings) {
var csrf_param = xhr.getResponseHeader('X-CSRF-Param');
var csrf_token = xhr.getResponseHeader('X-CSRF-Token');
if (csrf_param) {
$('meta[name="csrf-param"]').attr('content', csrf_param);
}
if (csrf_token) {
$('meta[name="csrf-token"]').attr('content', csrf_token);
}
});
That's it. YMMV depending on your Devise configuration. I suspect though that this issue is ultimately caused by the fact that the old CSRF token is killing the request, and rails throws an exception.