rails - "WARNING: Can't verify CSRF token authenticity" for json devise requests

How can I retrieve the CSRF token to pass with a JSON request?

I know that for security reasons Rails is checking the CSRF token on all the request types (including JSON/XML).

I could put in my controller skip_before_filter :verify_authenticity_token, but I would lose the CRSF protection (not advisable :-) ).

This similar (still not accepted) answer suggests to

Retrieve the token with <%= form_authenticity_token %>

The question is how? Do I need to do a first call to any of my pages to retrieve the token and then do my real authentication with Devise? Or it is an information one-off that I can get from my server and then use consistently (until I manually change it on the server itself)?


EDIT:

In Rails 4 I now use what @genkilabs suggests in the comment below:

protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format == 'application/json' }

Which, instead of completely turning off the built in security, kills off any session that might exist when something hits the server without the CSRF token.


skip_before_filter :verify_authenticity_token, :if => Proc.new { |c| c.request.format == 'application/json' }

This would turn off the CSRF check for json posts/puts that have properly been marked as such.

For example, in iOS setting the following to your NSURLRequest where "parameters" are your parameters:


[request setHTTPMethod:@"POST"];

[request setValue:@"application/json" 
       forHTTPHeaderField:@"content-type"];

[request setValue:@"application/json" 
       forHTTPHeaderField:@"accept"];

[request setHTTPBody:[NSData dataWithBytes:[parameters UTF8String] 
                                            length:[parameters length]]];

You can send the CSRF token, after a successful log-in, using a custom header.

E.g, put this in your sessions#create :

response.headers['X-CSRF-Token'] = form_authenticity_token

Sample log-in response header providing the CSRF token:

HTTP/1.1 200 OK
Cache-Control: max-age=0, private, must-revalidate
Connection: Keep-Alive
Content-Length: 35
Content-Type: application/json; charset=utf-8
Date: Mon, 22 Oct 2012 11:39:04 GMT
Etag: "9d719d3b9aabd413c3603e04e8a3933d"
Server: WEBrick/1.3.1 (Ruby/1.9.3/2012-10-12)
Set-Cookie: [cut for readability] 
X-Csrf-Token: PbtMPfrszxH6QfRcWJCCyRo7BlxJUPU7HqC2uz2tKGw=
X-Request-Id: 178746992d7aca928c876818fcdd4c96
X-Runtime: 0.169792
X-Ua-Compatible: IE=Edge

This Token is valid until you log-in again or (log-out if you support this through your API). Your client can extract and store the token from the log-in response headers. Then, each POST/PUT/DELETE request must set the X-CSRF-Token header with the value received at the log-in time.

Sample POST headers with the CSRF token:

POST /api/report HTTP/1.1
Accept: application/json
Accept-Encoding: gzip, deflate, compress
Content-Type: application/json; charset=utf-8
Cookie: [cut for readability]
Host: localhost:3000
User-Agent: HTTPie/0.3.0
X-CSRF-Token: PbtMPfrszxH6QfRcWJCCyRo7BlxJUPU7HqC2uz2tKGw=

Documentation: form_authenticity_token


Indeed simplest way. Don't bother with changing the headers.

Make sure you have:

<%= csrf_meta_tag %>

in your layouts/application.html.erb

Just do a hidden input field like so:

<input name="authenticity_token" 
       type="hidden" 
       value="<%= form_authenticity_token %>"/>

Or if you want a jquery ajax post:

$.ajax({     
    type: 'POST',
    url: "<%= someregistration_path %>",
    data: { "firstname": "text_data_1", "last_name": "text_data2", "authenticity_token": "<%= form_authenticity_token %>" },                                                                                  
    error: function( xhr ){ 
      alert("ERROR ON SUBMIT");
    },
    success: function( data ){ 
      //data response can contain what we want here...
      console.log("SUCCESS, data="+data);
    }
});

Basically when you post your json data just add a valid authenticity_token field to the post data and the warning should go away...


I resolved that error this way:

class ApplicationController < ActionController::Base
  protect_from_forgery
  skip_before_action :verify_authenticity_token, if: :json_request?

  protected

  def json_request?
    request.format.json?
  end
end

Source: http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html


What's worrying is that in Rails 3.2.3 we now get the CSRF warning in production.log but the post does not fail! I want it to fail as it protects me from attacks. And you can add the csrf token with jquery before filter btw:

http://jasoncodes.com/posts/rails-csrf-vulnerability