7

Is there a way to disable CSRF for all controllers, or does it have to be disabled on a per-controller basis? I am using ruby on rails as an API only and do not need any sort of CSRF as the requests aren't anywhere near session based. I'd like to disable just for JSON requests.

I believe this might work, but am unsure

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery
  skip_before_action :verify_authenticity_token, if: :json_request?

#Checks format for json
protected
  def json_request?
    request.format.json?
  end

end
steventnorris
  • 5,656
  • 23
  • 93
  • 174
  • It's the same as disabling it in an individual controller, but you'd do this in ApplicationController instead. – tadman Oct 15 '14 at 17:15
  • @tadman is there a way to specify only for json requests? I'm new to ruby on rails, so learning my way into use. – steventnorris Oct 15 '14 at 17:19
  • 1
    Set up a controller to serve as a base for all your API calls, disable it there, and then derive all your API controllers from that intermediate class. Same method applies. Make that controller JSON-only. That's the most conventional way to do it in Rails. – tadman Oct 15 '14 at 17:20
  • @tadman my question is not the same as the question you marked duplicate from. I want to disable for all controllers for only json requests. Also, is the code above a functional answer? If so, how does it compare to your suggested solution? – steventnorris Oct 15 '14 at 17:28
  • I've re-opened it if anyone has a more specific answer, but I think what you've got there should do the job. Only way to know for sure is to verify it in your environment. – tadman Oct 15 '14 at 17:31
  • If you have time @tadman, can you collect your comments into an answer? I would upvote it. – Jared Beck Oct 15 '14 at 17:43
  • @tadman Please do. I'll accept. It works for me. – steventnorris Oct 15 '14 at 17:45

1 Answers1

10

As with many things in Rails, disabling something in a base controller has the effect of disabling it in all those derived from it. To turn off CSRF completely, disable it in ApplicationController:

skip_forgery_protection

This is an alias for:

skip_before_action :verify_authenticity_token

The skip_before_action method does have options to customize how it's applied, so you can narrow down the focus on this:

skip_before_action :verify_authenticity_token, unless: csrf_required?

Where as you've shown you can define a method to restrict it. If that method returns true the action is executed as usual, otherwise it's skipped.

When writing an API it's common to have something like API::BaseController as an intermediate controller so you can separate session-based activity from API-based activity. For example:

class API::BaseController < ApplicationController
  skip_before_action :verify_authenticity_token
end

Then derive all your API-specific controllers from that one. Even in an application that's predominantly API driven, you may need a conventional "signup" page with a form submission on it, or an admin area with the ability to edit and update things.

One option I've discovered is to disable CSRF protection if an API key is supplied. For example:

def csrf_required?
  params[:api_key].blank?
end

That means you can still accept traditional "form-encoded" or XML API calls. If your API key is supplied via headers instead, as some require, you can adapt that to test against request accordingly.

tadman
  • 208,517
  • 23
  • 234
  • 262
  • 3
    Googlers note that using the `csrf_required?` method at the bottom, your non-API actions will be vulnerable to CSRF. The attacker only needs to add `?api_key=x` to the POST url and the CSRF check is disabled for the call. – Gabor Lengyel Jul 31 '18 at 01:53
  • @GaborLengyel Presumably you'd do validation against either the CSRF token *or* the API key and that you wouldn't let them through if the API key validation fails. – tadman Jul 31 '18 at 15:50
  • @tadman How do you suppose this would work? You would need to change api_key for every request and then it's not different from the CSRF token Rails implements in the first place. Otherwise you're open to replay attacks – idmean Jun 05 '22 at 08:58
  • @idmean If someone has your API key, you've been compromised. There's no need for a "replay attack". The request isn't signed with the API key. The API key is provided as authorization instead. The reason CSRF is necessary for browser-facing pages is protection from malicious pages. An API has no such issue. – tadman Jun 06 '22 at 19:36
  • @tadman I see. The key is an authorization token. The last example is a bit misleading with only `.blank?`. – idmean Jun 06 '22 at 20:11
  • @idmean What that means is if the API key is supplied, it's presumed to be validated elsewhere, but no CSRF key is required. This is for dual session or API-key driven APIs, such as you might have with GraphQL. – tadman Jun 08 '22 at 09:15