Rails 4 Authenticity Token
Asked Answered
L

13

200

I was working on a new Rails 4 app (on Ruby 2.0.0-p0) when I ran into some authenticity token problems.

While writing a controller that responds to json (using the respond_to class method), I got to the create action I started getting ActionController::InvalidAuthenticityToken exceptions when I tried to create a record using curl.

I made sure I set -H "Content-Type: application/json" and I set the data with -d "<my data here>" but still no luck.

I tried writing the same controller using Rails 3.2 (on Ruby 1.9.3) and I got no authenticity token problems whatsoever. I searched around and I saw that there were some changes with authenticity tokens in Rails 4. From what I understand, they are no longer automatically inserted in forms anymore? I suppose this is somehow affecting non-HTML content types.

Is there any way to get around this without having to request a HTML form, snatching the authenticity token, then making another request with that token? Or am I completely missing something that's completely obvious?

Edit: I just tried creating a new record in a new Rails 4 app using a scaffold without changing anything and I'm running into the same problem so I guess it's not something I did.

Leanto answered 28/4, 2013 at 2:25 Comment(0)
L
278

I think I just figured it out. I changed the (new) default

protect_from_forgery with: :exception

to

protect_from_forgery with: :null_session

as per the comment in ApplicationController.

# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.

You can see the difference by looking at the source for request_forgery_protecton.rb, or, more specifically, the following lines:

In Rails 3.2:

# This is the method that defines the application behavior when a request is found to be unverified.
# By default, \Rails resets the session when it finds an unverified request.
def handle_unverified_request
  reset_session
end

In Rails 4:

def handle_unverified_request
  forgery_protection_strategy.new(self).handle_unverified_request
end

Which will call the following:

def handle_unverified_request
  raise ActionController::InvalidAuthenticityToken
end
Leanto answered 28/4, 2013 at 15:35 Comment(7)
You could remove the :with option all together, :null_session is the default: api.rubyonrails.org/classes/ActionController/…Rampageous
Is there a way to use exception for non-JSON calls and null session for JSON calls (aka API calls)?Autolysis
@JamesMcMahon You may be able to write your own before_action that checks the format and whether the request is verified. I don't know of any built in way to set conditions.Leanto
In rails 4.1.6, I had to specify skip_before_action :verify_authenticity_token on my API's application controller as well to make this work.Chacon
You shouldn't have to disable :verify_authenticy_token any more in Rails 4.2. It defaults to :null_session, which, as the name implies, just gives you a request without a session. You can instead verify the request via an API key.Beet
I was running into the same problem even though my controller was already configured to allow null_sessions. The problem was my request. I was emulating it using the Advanced Rest Client on Chrome but I wasn't specifying the Content-Type header in my request, which should be set to "Application/JSON". After I notice this, everything worked fine. Hope this helps!Melessa
I had the same issue, but my problem was that I was just missing the rails command <%= csrf_meta_tags %> in my <head> tag.Caravan
J
73

Instead of turn off the csrf protection, it's better to add the following line of code into the form

<%= tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token) %> 

and if you're using form_for or form_tag to generate the form, then it will automatically add the above line of code in the form

Jagged answered 29/9, 2013 at 15:5 Comment(2)
This is good advice if you are making a form, but the question is referring to making API calls using JSON.Autolysis
Thank you though, this answered a question I had about hand-writing a code while using handlebars!Faunia
B
72

Adding the following line into the form worked for me:

<%= hidden_field_tag :authenticity_token, form_authenticity_token %>
Brandling answered 25/10, 2013 at 8:36 Comment(5)
doesn't really apply to api callsHelmholtz
In my case, I'm using Rails4. I can submit the form by clicking the submit button. But if i submit the form via JS code, this error occurs. And this answer fixed the problem for me.Solidago
For my Rails 4.0.8 based App it was enough to write =token_tag nil or (in .erb) <%= token_tag nil %>Floridafloridia
You seem to be disabling authentication by setting token to nil?Brandling
is it secure to do this?Vociferate
G
33

I don't think it's good to generally turn off CSRF protection as long as you don't exclusively implement an API.

When looking at the Rails 4 API documentation for ActionController I found that you can turn off forgery protection on a per controller or per method base.

For example to turn off CSRF protection for methods you can use

class FooController < ApplicationController
  protect_from_forgery except: :index
Goles answered 7/8, 2013 at 5:44 Comment(1)
This did it for me in Rails 4 - error being thrown after attempting to delete. ^^Trapeze
A
9

Came across the same problem. Fixed it by adding to my controller:

      skip_before_filter :verify_authenticity_token, if: :json_request?
Atrabilious answered 19/12, 2014 at 19:59 Comment(2)
undefined method `json_request?' for #<SettingsController:0x000000030a54a8>, Any idea?Igal
You have to define a method there like: def json_request? request.format.json? endOdelsting
L
7

Did you try?

 protect_from_forgery with: :null_session, if: Proc.new {|c| c.request.format.json? }
Lomond answered 1/4, 2015 at 13:1 Comment(0)
B
5

This official doc - talks about how to turn off forgery protection for api properly http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html

Berck answered 19/11, 2015 at 22:28 Comment(1)
That's true, but the answer is from 2013 - and things change. And while your officially accepted answer is good - my link simply gives better in depth overview of the subjectBerck
I
3

This is a security feature in Rails. Add this line of code in the form:

<%= hidden_field_tag :authenticity_token, form_authenticity_token %>

Documentation can be found here: http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html

Infatuated answered 3/2, 2016 at 5:34 Comment(0)
W
2

These features were added for security and forgery protection purposes.
However, to answer your question, here are some inputs. You can add these lines after your the controller name.

Like so,

class NameController < ApplicationController
    skip_before_action :verify_authenticity_token

Here are some lines for different versions of rails.

Rails 3

skip_before_filter :verify_authenticity_token

Rails 4:

skip_before_action :verify_authenticity_token


Should you intend to disable this security feature for all controller routines, you can change the value of protect_from_forgery to :null_session on your application_controller.rb file.

Like so,

class ApplicationController < ActionController::Base
  protect_from_forgery with: :null_session
end
Welker answered 19/12, 2016 at 10:7 Comment(0)
S
1

If you're using jQuery with rails, be wary of allowing entry to methods without verifying the authenticity token.

jquery-ujs can manage the tokens for you

You should have it already as part of the jquery-rails gem, but you might need to include it in application.js with

//= require jquery_ujs

That's all you need - your ajax call should now work

For more information, see: https://github.com/rails/jquery-ujs

Sihon answered 5/6, 2017 at 15:19 Comment(0)
D
1

Add authenticity_token: true to the form tag

Davena answered 16/8, 2017 at 14:56 Comment(0)
G
0

When you define you own html form then you have to include authentication token string ,that should be sent to controller for security reasons. If you use rails form helper to generate the authenticity token is added to form as follow.

<form accept-charset="UTF-8" action="/login/signin" method="post">
  <div style="display:none">
    <input name="utf8" type="hidden" value="&#x2713;" />
    <input name="authenticity_token" type="hidden" value="x37DrAAwyIIb7s+w2+AdoCR8cAJIpQhIetKRrPgG5VA=">
  </div>
    ...
</form>

So the solution to the problem is either to add authenticity_token field or use rails form helpers rather then compromising security etc.

Galwegian answered 6/7, 2014 at 12:10 Comment(4)
Thank you for your answer, though this does not answer the original question. The question asked about responding to JSON requests but you are providing a solution for a from-scratch HTML form. When making JSON requests (think API) you are not submitting a HTML form and you may not have easy access to the authenticity token.Leanto
unless the content of the request is actually JSON in which case you would want to set it to application/json.Leanto
I understand the answer is not exactly what you are looking for. But the purpose of putting related answer is to help users looking for similar "Authenticity Issues".Galwegian
sorry I removed mistakenly - "you can set Content-Type: application/x-www-form-urlencoded with above solution".Galwegian
Y
0

All my tests were working fine. But for some reason I had set my environment variable to non-test:

export RAILS_ENV=something_non_test

I forgot to unset this variable because of which I started getting ActionController::InvalidAuthenticityToken exception.

After unsetting $RAILS_ENV, my tests started working again.

Yoong answered 21/11, 2017 at 8:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.