Jquery – Rails not reloading session on ajax post

ajaxjqueryruby-on-rails

I'm experiencing a very strange problem with Rails and ajax using jQuery (although I don't think it's specific to jQuery).

My Rails application uses the cookie session store, and I have a very simple login that sets the user id in the session. If the user_id isn't set in the session, it redirects to a login page. This works with no problems. JQuery GET requests work fine too. The problem is when I do a jQuery POST – the browser sends the session cookie ok (I confirmed this with Firebug and dumping request.cookies to the log) but the session is blank, i.e. session is {}.

I'm doing this in my application.js:

$(document).ajaxSend(function(e, xhr, options) {
  var token = $("meta[name='csrf-token']").attr('content');
  xhr.setRequestHeader('X-CSRF-Token', token);
});

and here is my sample post:

$.post('/test/1', { _method: 'delete' }, null, 'json');

which should get to this controller method (_method: delete):

def destroy
  respond_to do |format|
    format.json { render :json => { :destroyed => 'ok' }.to_json }
  end
end

Looking at the log and using Firebug I can confirm that the correct cookie value is sent in the request header when the ajax post occurs, but it seems that at some point Rails loses this value and therefore loses the session, so it redirects to the login page and never gets to the method.

I've tried everything I can think of to debug this but I'm coming around to the idea that this might be a bug in Rails. I'm using Rails 3.0.4 and jQuery 1.5 if that helps. I find it very strange that regular (i.e. non-ajax) get and post requests work, and ajax get requests work with no problems, it's just the ajax posts that don't.

Any help in trying to fix this would be greatly appreciated!

Many thanks,
Dave

Best Answer

I'm going to answer my own question as I've managed to work out what was going on. I'll post it here in case it's useful to anyone else!

After investigating further, I worked out that the code that was supposed to be setting the request header with the CSRF token, wasn't. This was the original code:

$(document).ajaxSend(function(e, xhr, options) {
  var token = $("meta[name='csrf-token']").attr('content');
  xhr.setRequestHeader('X-CSRF-Token', token);
});

What was happening was that this code wasn't setting the header, Rails was receiving an Ajax request, the token didn't match and it was resetting the session. This used to raise an ActionController::InvalidAuthenticityToken error (I suppose I would have caught this earlier if an error was raised... oh well), but since Rails 3.0.4 it now just quietly resets the session.

So to send the token in the header, you have to do this (many thanks to this marvellous blog post):

$.ajaxSetup({
  beforeSend: function(xhr) {
    xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
  }
}); 

And now it all works as it should. Which is nice.