5

Is there any provisions in rails that would allow all AJAX POST requests from the site to pass without an authenticity_token?

I have a Jquery POST ajax call that calls a controller method, but I did not put any authenticity code in it and yet the call succeeds.

My ApplicationController does have 'request_forgery_protection' and I've changed

config.action_controller.consider_all_requests_local

to false in my environments/development.rb

I've also searched my code to ensure that I was not overloading ajaxSend to send out authenticity tokens.

Is there some mechanism in play that disables the check? Now I'm not sure if my CSRF protection is working or not.

I'm using Rails 2.3.5.

Update for clarity:

function voteup(url, groupid){
      $.ajax({
        type: "POST",
        url: "/groups/" + groupid + "/submissions/voteup",
        data: "url=" + url,
        dataType: 'text',
        success: function(data){
          var counter = "vote_" + url;
          $('#vote_' + url.cleanify()).text(" " + data + " ");
        }
      });
    };

I have a link which then has a 'href that calls the above function:

<a href='javascript:voteup(param1,param2)'>...</a>
David C
  • 3,659
  • 5
  • 34
  • 46
  • I remembered answering a similar question.. found it here: http://stackoverflow.com/questions/2725118/rails-request-forgery-protection-settings/2725991#2725991 I guess it should be because its a local request. It should not work say from an app running on a different port or a different domain. Ex: If you have your app running on localhost:3000 you should not be able to do a ajax POST from localhost:3001. – Shripad Krishna Jun 17 '10 at 10:52
  • Hi Shripad, I saw your post, but how is this happening? Is there some kind of Rails magic involved? Looking online, I still see people writing about instructions to send form_authenticity_token, so if its fixed, why is there still a need to do that? – David C Jun 18 '10 at 03:43
  • This has nothing to do with Rails magic whatsoever. Ajax works without a authenticity token if it is invoked within the domain itself. You need a authenticity token not for enabling ajax to work on your domain but to prevent attacks from other domains. That is the primary reason for having `protect_from_forgery` in your application controller. The best example of an attack from another domain is the "Myspace sammy worm". Look it up. You'll then understand as to why you need a auth token. – Shripad Krishna Jul 05 '10 at 03:32

2 Answers2

7

A likely scenario here is that you're using jQuery to serialize a normal Rails form... and it is including in that the serialized auth token hidden field (Rails adds them to all forms).

Look at your generated source for the form you're submitting... it's likely you'll see

<input name="authenticity_token" type="hidden" value="somethinghere...." />

The other thing you can do is check the log to see if the authenticity_token field is in the request params.

mylescarrick
  • 1,680
  • 8
  • 10
  • 1
    I've updated the question with some code samples. My logs doesn't show an authenticity_token param being sent. – David C Jun 17 '10 at 07:19
3

Add this in your layout view: app/views/layouts/(something).html.erb

<% if protect_against_forgery? %>
 <script type="text/javascript" charset="utf-8">
  //<![CDATA[
   window._auth_token_name = "#{request_forgery_protection_token}";
   window._auth_token = "#{form_authenticity_token}";
  //]]>
 </script>
<% end %>

And in your application.js :

$.ajaxSetup({
  'beforeSend': function(xhr) {xhr.setRequestHeader("Accept", "text/javascript")}
});

$(document).ready(function() {
 //  All non-GET requests will add the authenticity token
 //  If not already present in the data packet
 $("body").bind('ajaxSend', function(elm, xhr, s) {
   if (s.type == "GET") return;
   if (s.data && s.data.match(new RegExp("\\b" + window._auth_token_name + "="))) return;
   if (s.data) {
  s.data = s.data + "&";
  } else {
   s.data = "";
   // if there was no data, $ didn't set the content-type
   xhr.setRequestHeader("Content-Type", s.contentType);
  }
  s.data = s.data + encodeURIComponent(window._auth_token_name) + "=" + encodeURIComponent(window._auth_token);
 });

In your application_controller.rb add this to ensure IE/SAFARI receive correct accept headers:

 def correct_safari_and_ie_accept_headers
    ajax_request_types = [ 'text/javascript', 'application/json', 'text/xml']
        request.accepts.sort!{ |x, y| ajax_request_types.include?(y.to_s) ? 1 : -1 } if request.xhr?
 end

This ensures that the accept header default to text/javascript, application/json or text/xml instead of the default text/html.

Now you can perform your regular AJAX calls. It will pass the auth token. Also make sure you include application.js only after the CDATA script tag as it requires the two global vars window._auth_token_name and window._auth_token.

Hope this helps! Cheers :)

Shripad Krishna
  • 10,463
  • 4
  • 52
  • 65
  • thanks for the code, but my problem is that at this point of time, my posts work even without an auth token. – David C Jun 17 '10 at 09:03