Rails 4 skipping protect_from_forgery for API actions

user44484 picture user44484 · May 21, 2014 · Viewed 16.8k times · Source

I've been implementing a Rails 4 application with an API. I want to be able to call the API from mobile phones and the webapp itself. I came across this note while researching protect_from_forgery:

It's important to remember that XML or JSON requests are also affected and if you're building an API you'll need something like:

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

I was thinking of doing this, but I have some reservations/questions:

  1. This solution seems to leave the CSRF hole open because now an attacker could craft a link with an onclick javascript that posts JSON?
  2. Would checking for an API token be a reasonable substitute? i.e., what if instead of skipping the authenticity check, allowing it to fail the check and recover in handle_unverified_request if the api token is present and correct for current user?
  3. Or maybe I should just make the webapp and mobile devices send the CSRF token in the HTTP headers? Is that safe? How would the mobile phone even obtain the CSRF token, given that it isn't rendering HTML forms to begin with?

Edit for clarification:

I am more concerned about the webapp user clicking a crafted CSRF link. The mobile users are authenticated, authorized, an have an API key, so I am not concerned about them. But by enabling CSRF protection for the webapp users, the mobile users are blocked from using the protected API. I want to know the correct strategy for handling this, and I don't believe the Rails documentation gives the right answer.

Answer

Oren Mazor picture Oren Mazor · May 21, 2014

An attacker could CURL at your controllers all they like, but if your API requires authentication, they wont get anywhere.

Making the API consumers send a CSRF is not really what CSRF does. To do this you'd need to implement a type of knocking mechanism where your client hits an authorization endpoint first to get the code (aka CSRF) and then submit it in the POST. this sucks for mobile clients because it uses their bandwidth, power, and is laggy.

And anyway, is it actually forgery (i.e. the F in CSRF) if its an authorized client hitting your controller after all?