5
votes

I want to build a rails app with two different protect_from_forgery strategies: one for the web application, and one for the API.

In my application controller I have this line of code: protect_from_forgery with: :exception in order to prevent CSRF attacks, it works just fine.

In my API namespace, I created an api_controller that inherits from my application controller, and that is the parent class of all the other controllers in the API namespace, and I changed the code above with: protect_from_forgery with: :null_session.

Sadly, I have an error when trying to make POST request: "Can't verify CSRF token authenticity".

I don't want to skip the verify_authenticity_token method in my API controllers, I just want to have two distinct strategies in my app, so how do I override the protect_from_forgery strategy defined in my application controller ?

Edit: Ok, so I eventually did what I did not want to do in the first place: change the inheritance of my api_controller: it now inherits from ActionController::Base, and no longer from my application controller. It does work now but:

  1. It does not answer my question i.e. overriding the protect_from_forgery strategy.
  2. It is not DRY as I have to copy/past what was previously in my application_controller.

So if anyone has a real way to overwrite this method, I'd appreciate it.

3

3 Answers

13
votes

What if you leave the protect_from_forgery with: :exception in the application controller but then you put the following in your API controller?

skip_before_action :protect_from_forgery
protect_from_forgery with: :null_session

That way, you still get the standard CSRF attack protection for all controllers in your web application but you also get the null session behavior for your API methods.

6
votes

I am running an application with a similar structure - Web App + API. I solved the CSRF problem like this:

  • Apply protect_from_forgery only for non API requests
  • My API endpoint is api.example.com, so I used subdomain constraint to distinguish API and web app requests

Code:

class ApplicationController < ActionController::Base

  protect_from_forgery with: :exception, if: :isWebRequest?

  def isWebRequest?
    request.subdomains[-1] != 'api'
  end

end
1
votes

Late to the party, but something like this can be done:

class YourCustomStrategy
  def initialize(controller)
  end

  def handle_request
  end
end

And in your ApplicationController or where you want:

class ApplicationController < ActionController::Base
 protect_from_forgery with: YourCustomStrategy
end