Search code examples
ruby-on-railsrubyajaxdevise

Devise and AJAX: Can't verify CSRF token authenticity after sign in


Heyo,

I'm using Rails 4 and Devise, and have overridden the Devise controllers to allow AJAX sign ins and registration. It's a one-page app. When a user registers, they can perform all necessary AJAX POSTs. When they sign IN, however, they can't - I get "Can't verify CSRF token authenticity" on the server side. This happens until I refresh the page - then the CSRF token updates and I can POST normally.

Any idea why this just happens after a registered user signs in, and not on after registration or a page refresh? Here's the code in my controllers:

class SessionsController < Devise::SessionsController
  def create
    resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#failure")
    sign_in_and_redirect(resource_name, resource)
  end

  def sign_in_and_redirect(resource_or_scope, resource=nil)
    scope = Devise::Mapping.find_scope!(resource_or_scope)
    resource ||= resource_or_scope
    sign_in(scope, resource) unless warden.user(scope) == resource
    @badge = resource.badge
    return render 'signin.js.erb'
  end

  def failure
    return render :json => {:success => false, :errors => ["Login failed."]}
  end

  def destroy
    redirect_path = '/labs'
    signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))    
    yield resource if block_given?

    # We actually need to hardcode this as Rails default responder doesn't
    # support returning empty response on GET request
    respond_to do |format|
      format.all { head :no_content }
      format.any(*navigational_formats) { redirect_to redirect_path }
    end
  end
end

And my Registration controller:

class RegistrationsController < Devise::RegistrationsController

  def create
    build_resource(sign_up_params)

    if resource.save
      if resource.active_for_authentication?
        set_flash_message :notice, :signed_up if is_navigational_format?
        sign_up(resource_name, resource)
        return render 'signup.js.erb'
      else
        set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_navigational_format?
        expire_session_data_after_sign_in!
        return render :json => {:success => true}
      end
    else
      clean_up_passwords resource
      return render :json => {:success => false}
    end
  end

  # Signs in a user on sign up. You can overwrite this method in your own
  # RegistrationsController.
  def sign_up(resource_name, resource)
    sign_in(resource_name, resource)
  end

end

Annnd the AJAX post in question:

$(document).on('click', '.project_item', function () {
    $.ajax({
      type: "POST",
      url: '/hammer/thing_toggle',
      data: 'id=' + $(this).data('id')
    });
});

Solution

  • Well, I figured it out. It's kind of hacky, but it does the trick.

    When a user signs in, I load some HTML that contains a new <%= csrf_meta_tags %> element, and force the AJAX requests to use that new token:

    $(document).on('click', '.project_item', function () {
        $.ajax({
          type: "POST",
          beforeSend: function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').last().attr('content'))},
          url: '/hammer/thing_toggle',
          data: 'id=' + $(this).data('id')
        });
    });
    

    It's strange that the csrf_meta_tags token value changes when signing in VIA ajax, but not when I register a user VIA ajax. Strange, indeed... If anyone has any insight into why this is, it'd be appreciated!