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')
});
});
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!