How to make sure Rails API is secured from CSRF? - ruby-on-rails

I've been developing Rails app with REST API for access from mobile application.
It works quite well. When user logs in from mobile application, he gets auth_token that he uses in his future requests to API. The issue is that API is also accessible from web by going to path /api/v1/... and because of this, it has to be protected from CSRF.
I have BaseApiController class which inherits from ApplicationController that has protect_from_forgery "enabled". Here's example:
class Api::V1::BaseApiController < ApplicationController
# ...
end
class ApplicationController < ActionController::Base
protect_from_forgery
# ...
end
Now, when I do non-GET requests to my API, with auth_token, my request gets completed successfully, but in the logs I can see the famous WARNING: Can't verify CSRF token authenticity. If I remove protect_from_forgery from my BaseApiController, I don't get any warnings (obviously), but then my API is vulnerable to CSRF attacks (I made a simple HTML form that successfully changes the data across domains when there's no protect_from_forgery).
My question is: How to assure my API stays secure, but also remove the warning when doing non-GET requests?
Here's one of the solutions I've come up with, but it looks more like a hack and executes one extra DB query:
class Api::V1::BaseApiController < ApplicationController
# ...
def verified_request?
super || User.where(authentication_token: params['auth_token']).count > 0
end
end
More details about the project: Rails 3.2.14, Devise, AngularJS. The project's source code can be found here.

You may see people suggest that CSRF is not an issue for API requests (there is no state to begin with, so what is there to hijack anyhow?), so some suggest the following to simply eliminate the warning:
skip_before_filter :verify_authenticity_token, :only => [:your_method]
However, there was some commentary that it is possible to commit CSRF with text/plain using various Flash and Java-based methods. I believe that was the reason for the security patch in rails a while back: http://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails/
In any event, a good solution that actually checks for an authenticity token can be found here: WARNING: Can't verify CSRF token authenticity rails
It involves actually setting the header in your request.
Good luck!

Related

Rails 6 `protect_from_forgery with: :null_session` not working

In an effort to learn React and using Rails together I am in the process of building a simple "Todo" app. Im using axios to make requests to my rails api within react components.
So far everything was working fine until I made my first POST request. Im pointing to the right place and sending fine params but saw I was getting Can't verify CSRF token authenticity.. Upon further googling it appears that adding:
class ApplicationController < ActionController::Base
protect_from_forgery with: :null_session
end
Would fix my issue. But despite server restarts/etc... I still receive the same error when sending a POST request. Also adding prepend: true does not help either (as was suggested by another member).
Is there something im missing? This seems like a straightforward problem from what i've read but it doesn't seem to be working for me.
Im on Rails 6.0
if you just want the error to go away, use:
class ApplicationController < ActionController::Base
skip_forgery_protection
end
But if you would like to actually take advantage of the Rails forgery protection, then add an "X-CSRF-Token" header to your post request from React. The value for this header can be retrieved by javascript from the page's head element, in a meta tag included by Rails. For example (using jQuery):
var csrf_token_value = $('meta[name="csrf-token"]').attr('content')

Devise InvalidAuthenticityToken

I am trying to create an authentication system with devise. When I try to login with a post request, without ajax, it gives me the "ActionController::InvalidAuthenticityToken" Error.
I've seen that this is supposed to be a common bug with devise. I've seen some usual approaches to it, which don't work for me:
To include an csrf token on the html: It is already included in the login site
Include a hackish solution like this: skip_before_action :verify_authenticity_token, if: -> { controller_name.include? 'sessions' }
. Unfortunately this doesn't change a thing. In the original solution I've seen it suggested to include it into Users::SessionsController. I can change whatever I like in this controller or even delete the whole controller and it still works the same.
This is supposed to be a problem with protecting from forgery according to other sources, but I can literally remove it from the application controller and I still have the same error.
class ApplicationController < ActionController::Base
# protect_from_forgery with: :exception
end
I believe that Devise checks the token independently from the rest of my app. This would also explain why it doesn't work even though there is a token at the sign in form. I have no clue what to do about it though.

Devise and CSRF

I am getting a CSRF Warning in my Rails logs intermittently. I think I have resolved the issue but would like some advice as to whether the solution is sound from a security point of view.
It's a Rails 4 app with Devise application with some Angular JS. Sometimes on a certain page the user session can time out and then we make an AJAX request (though I can see this in normal html requests too it just mainfests itself more regularly in JS) We get a 401 in the logs and can handle from that point. The problem is before that before that 401 we are getting the following message:
WARNING: Can't verify CSRF token authenticity
which is noisy and so I don't know if there is a real attack going on. They're logged out and their token is, I believe, no longer valid.
I believe this is because in Application Controller protect_from_forgery happens before my devise authenticate_user! filter. So:
class ApplicationController < ActionController::Base
protect_from_forgery
etc....
end
Happens before
class MyController < ApplicationController
before_filter :authenticate_user!
def new
end
end
If I change the above to prepend_before_action :authenticate_user! the CSRF message goes away. My question is there a (security) reason to not do this? I haven't seen other people do this so assume they must be getting this message too? It makes sense to me that Devise is one of the very first things that happens in our filter chain but don't see that as common practice.
Thanks in advance :)

Using curl to access backend API of Rails app that implements devise

We have a new web app constructed by another developer. My job was to implement a backend API for other programs to use to exchange XML data with the database. It was going pretty well, until the main app implemented devise to authenticate users. I figured I'd be able to tweak the curl command to log in then submit/request data, but it isn't going very well. I'm trying things like
curl --cookie-jar ~/Desktop/cjar --data "user[login]=<username>" --data "user[password]=<pwd>" --data "commit=Sign in" localhost:3000/users/sign_in
followed by
curl --cookie ~/Desktop/cjar --data "<root_node></root_node>" localhost:3000/my_update_method
my latest error message is
WARNING: Can't verify CSRF token authenticity
Completed 401 Unauthorized in 1ms
I guess that's better than the redirection messages I was getting, but I'm still not close to knowing what's going on.
I've found a few related posts that make me think this is partly related to devise, and partly related to automatic authentication done by Rails. When I look at the info being passed by the web page, I see authenticity_token and utf8 params that I don't know how to construct for my curl command.
I can keep trying stuff, but I'm new to both curl and authentication, so I'm sort of shooting in the dark, and hoping that someone can save me some time. I guess my questions are:
Can this be done?
Should this be done? If not, what other options do I have?
One post suggested a way around the authentication, but is is possible for only the curl commands to require a login/pwd, but bypass any other validation, without affecting the main web app?
UPDATE 1:
Thanks for all the help, I appreciate it. The response to the first curl command (listed above) is now just a redirection to the login page, so logging in doesn't seem to take. Does anyone have any suggestions about where to start debugging that? Nothing is ever getting into the application, so there's no logging there to look at. Here's the content of the cookie, if it means anything to anyone:
# Netscape HTTP Cookie File
# http://curl.haxx.se/rfc/cookie_spec.html
# This file was generated by libcurl! Edit at your own risk.
#HttpOnly_localhost FALSE / FALSE 0 _glow_session BAh7B0kiCmZsYXNoBjoGRUZvOiVBY3Rpb25EaXNwxhc2g6OkZsYXNoSGFzaAk6CkB1c2VkbzoIU2V0BjoKQGhhc2h7ADoMQGNsb3NlZEY6DUBmbGFzaGVzewY6C25vdGljZUkiHFNpZ25lZCBpbiBzdWNjZXNzZnVsbHkuBjsAVDoJQG5vdzBJIg9zZXNzaW9uX2lkBjsARkkiJTIwODc1Y2VjM2ViNzlmZDE3ZjA4ZjVmMDAxNWMxMDU4BjsAVA%3D%3D--d90722b6da386630b33f57902447b440f30d0b2a
I've added skip_before_filter :verify_authenticity_token to the controller that handles the api requests, and that eliminated the error I was seeing before.
UPDATE 2:
After some troubleshooting and debugging, I think I may actually be getting logged in okay, but when I execute the second curl, the development.log file shows
Started POST "/upsert_experiment" for 127.0.0.1 at 2013-10-16 12:14:12 -0500
Processing by WebServiceController#upsert_experiment as */*
Parameters: {"experiment_xml"=>"<experiment></experiment>"}
Completed 401 Unauthorized in 1ms
which leads me to think that the authorization is still the problem.
UPDATE 3:
I think the problem is just that skip_before_filter :verify_authenticity_token isn't working.
I have
class ApplicationController < ActionController::Base
rescue_from DeviseLdapAuthenticatable::LdapException do |exception|
render :text => exception, :status => 500
end
protect_from_forgery
before_filter :authenticate_user!
end
and
class WebServiceController < ApplicationController
skip_before_filter :verify_authenticity_token
...
end
With this setup, I get "Completed 401 Unauthorized in 1ms". If I comment out the protect_from_forgery line in the superclass, it just works, so the forgery protection is definitely the problem. I just don't know how to solve it.
Rails CSRF protection is preventing you from making this request.
From rails documentation
CSRF protection automatically include a security token, calculated from the current session and the server-side secret, in all forms and Ajax requests generated by Rails. You won't need the secret, if you use CookieStorage as session storage. If the security token doesn't match what was expected, the session will be reset. Note: In Rails versions prior to 3.0.4, this raised an ActionController::InvalidAuthenticityToken error.
If you want to skip it, add this line to your controller
skip_before_action :verify_authenticity_token, :only => ["your_update_action"]
CSRF stands for Cross Site Request Forgery and is a security protection you can read more about here. Essentially rails generates a random token included in the form rendered on the page and it expects to get that token back on form submit. You don't need this protection for an api, but it is important for the web app side. See this question for methods to disable the CSRF token.
One approach you might take is to use the token_authenticable Devise module and authenticate your requests with auth_token=#{token}, then skip CSRF protection if an auth token is present.
You're missing a CSRF token. This is to prevent cross site request forgery.

Setting HTTP Headers for Rails form_for

I am currently working on an avatar app powered by Rails where users can upload avatars for their user profile.
I would like to use a custom HTTP header to block public upload requests and only allow requests from my apps. How would I go about doing this with Ruby on Rails?
I am uploading the avatars using AJAX so this may be a bit harder. Also I would prefer not to show the header in the public HTML code otherwise it defeats the object of adding it!
If you add
protect_from_forgery
to your application controller, it will block all NON Get requests from 3rd party links. It will add a hidden input value to each form with an authentication token that will be used to check all data that is sent to the servers.
Further reading
http://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf
http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html
Rails 3.1 - CSRF ignored?
You could implement a custom HTTP header (say X-Foobar-Validity-Status: valid) and check it in a before_filter.
class YourController < ApplicationController
before_filter :check_header
def check_header
unless request.headers['X-Foobar-Validity-Status'] == "valid"
render json: {"error" => "You are an evil attacker. Go away"}
end
end
end
However, I would consider this a bad idea.
Attackers can read the packet dump of your HTTP requests and add the headers, even with jQuery. See the jQuery.ajax headers option.
Instead of using a proprietary header, I would use User-Agent for this purpose.
Instead, I would sugest using the protect_from_forgery mechanism of rails. It makes your life easier and is more secure. Just fetch the authenticy token by a http request in your app and then send it back with your request. This should keep intruders out.

Resources