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:

  1. Rails includes a CSRF token inside the head tag of your page.
  2. Rails evaluates this CSRF token anytime you perform a POST, PATCH or DELETE request.
  3. 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.