132

I want to make POST request to my local dev, like this:

  HTTParty.post('http://localhost:3000/fetch_heroku',
                :body => {:type => 'product'},)

However, from the server console it reports

Started POST "/fetch_heroku" for 127.0.0.1 at 2016-02-03 23:33:39 +0800
  ActiveRecord::SchemaMigration Load (0.0ms)  SELECT "schema_migrations".* FROM "schema_migrations"
Processing by AdminController#fetch_heroku as */*
  Parameters: {"type"=>"product"}
Can't verify CSRF token authenticity
Completed 422 Unprocessable Entity in 1ms

Here is my controller and routes setup, it's quite simple.

  def fetch_heroku
    if params[:type] == 'product'
      flash[:alert] = 'Fetch Product From Heroku'
      Heroku.get_product
    end
  end

  post 'fetch_heroku' => 'admin#fetch_heroku'

I'm not sure what I need to do? To turn off the CSRF would certainly work, but I think it should be my mistake when creating such an API.

Is there any other setup I need to do?

ZK Zhao
  • 19,885
  • 47
  • 132
  • 206
  • 6
    For APIs is generally accepted to turn off _CSRF_ token validation. I use `protect_from_forgery with: :null_session`. – dcestari Feb 03 '16 at 15:44
  • [Possibly](https://stackoverflow.com/q/20875591/4575793) [related](https://stackoverflow.com/q/66827240/4575793) [here](https://stackoverflow.com/q/63911493/4575793). For me, it helped to give an authenticityToken from the controller to the frontend and just give that back to the backend unchanged when the POST request is done. – Cadoiz Mar 15 '23 at 07:57

10 Answers10

146

Cross site request forgery (CSRF/XSRF) is when a malicious web page tricks users into performing a request that is not intended for example by using bookmarklets, iframes or just by creating a page which is visually similar enough to fool users.

The Rails CSRF protection is made for "classical" web apps - it simply gives a degree of assurance that the request originated from your own web app. A CSRF token works like a secret that only your server knows - Rails generates a random token and stores it in the session. Your forms send the token via a hidden input and Rails verifies that any non GET request includes a token that matches what is stored in the session.

However in an API thats intended to be used cross site and even serve non-browser clients its not very useful due to the problems with cross-domain cookies and providing CSRF tokens.

In that case you should use a token based strategy of authenticating API requests with an API key and secret since you are verifying that the request comes from an approved API client - not from your own app.

You can deactivate CSRF as pointed out by @dcestari:

class ApiController < ActionController::Base
  protect_from_forgery with: :null_session
end

Updated. In Rails 5 you can generate API only applications by using the --api option:

rails new appname --api

They do not include the CSRF middleware and many other components that are superflouus.

max
  • 96,212
  • 14
  • 104
  • 165
  • Thanks, I choose to turn part of the CSRF off: http://stackoverflow.com/questions/5669322/turn-off-csrf-token-in-rails-3 – ZK Zhao Feb 04 '16 at 01:16
  • 7
    this suggests that all APIs serve application-to-application traffic (in which a single partner is issued a unique key & secret). In that scenario, like a server-to-server communication, this answer is appropriate. HOWEVER, what is confusing to most web app developers is that for a Javascript client, controlled and written by you, you DO NOT WANT TO use a single key-secret (that would expose a single key-secret to all clients). Instead, Rail's CSRF and cookie session mechanism work great— even for Javascript apps that use your API— if you pass the CSRF token back to Rails with each request. – Jason FB Feb 16 '19 at 18:48
  • 1
    the way you do this in AJAX is described in this SO post https://stackoverflow.com/questions/7203304/warning-cant-verify-csrf-token-authenticity-rails – Jason FB Feb 16 '19 at 18:56
  • @JasonFB you are correct in that a single secret with javascript clients don't work. However using sessions and the Rails CSRF protection is still problematic if you intend to build an API that can be also be used by other types of non-browser clients. – max Mar 12 '19 at 23:45
  • If you provide or build an alternative to CSRF, be my guest. Just know the reason for what you are replacing so you know what is appropriate to replace it with. Obviously, now our quibble become contextual. – Jason FB Jun 28 '19 at 14:02
118

Another way to turn off CSRF that won't render a null session is to add:

skip_before_action :verify_authenticity_token

in your Rails Controller. This will ensure you still have access to session info.

Again, make sure you only do this in API controllers or in other places where CSRF protection doesn't quite apply.

Soviut
  • 88,194
  • 49
  • 192
  • 260
Matt Waldron
  • 1,738
  • 1
  • 14
  • 11
26

There is relevant info on a configuration of CSRF with respect to API controllers on api.rubyonrails.org:

It's important to remember that XML or JSON requests are also affected and if you're building an API you should change forgery protection method in ApplicationController (by default: :exception):

class ApplicationController < ActionController::Base
  protect_from_forgery unless: -> { request.format.json? }
end

We may want to disable CSRF protection for APIs since they are typically designed to be state-less. That is, the request API client will handle the session for you instead of Rails.

Mr. Tao
  • 843
  • 8
  • 17
  • 4
    This is so confusing. I am seesawing between sources that say that `protect_from_forgery` is still necessary for APIs, and sources that say that it isn't. What if the API is for a single page app, that uses the session cookie for user authentication? – Wylliam Judd Dec 21 '18 at 02:07
  • The CSRF mechanism is Rails' built-in way to deal with the attack vector using session cookies. The mechanism protects your controllers, while operating alongside (actually inside) of the Rails session cookie. Without protect_from_forgery, or turning it off or setting an except on it, you are telling Rails NOT to protect that action using the information from the CSRF token (which is taken from the session cookie). – Jason FB Feb 16 '19 at 18:44
  • 5
    I think what is confusing is that for Rails documentation "API" means a server-to-server application that will receive API requests from trusted, remote partners (not all web users who visit your site). For those ppl, give unique key-secret pairs via a secure non-hackable mechanism. For modern web applications, where lots of people will load your web app via a web client, you still use a session or token-based mechanism to uniquely identify each person who visits your site. So unless you're using SOME OTHER mechanism to do this (Json Web tokens, etc), stick with Rails' built-in stuff. – Jason FB Feb 16 '19 at 18:53
  • 1
    the way you do this in AJAX is described in this SO post https://stackoverflow.com/questions/7203304/warning-cant-verify-csrf-token-authenticity-rails – Jason FB Feb 16 '19 at 18:56
24

Since Rails 5 you can also create a new class with ::API instead of ::Base:

class ApiController < ActionController::API
end
webaholik
  • 1,619
  • 1
  • 19
  • 30
10

If you're using Devise, please note that

For Rails 5, protect_from_forgery is no longer prepended to the before_action chain, so if you have set authenticate_user before protect_from_forgery, your request will result in "Can't verify CSRF token authenticity." To resolve this, either change the order in which you call them, or use protect_from_forgery prepend: true.

Documentation

Kaka Ruto
  • 4,581
  • 1
  • 31
  • 39
8

If you are doing an api base website without csrf authentication, just put this in the controller

skip_before_action :verify_authenticity_token

But this expose the api endpoints to everyone

To overcome this issue, in our app(which is api based + front end in react) we are passing this in the headers

.
.
headers: {
      "Content-Type": "application/json",
      "X-CSRF-Token": document.querySelector("meta[name='csrf-token']").content,
    },
.
.
Sadman Ahmed
  • 131
  • 1
  • 3
  • Passing in the `X-CSRF-Token` from our frontend JS POST request solved the issue for us, thank you! – Bek Mar 13 '23 at 22:33
5

If you want to exclude the sample controller's sample action

class TestController < ApplicationController
  protect_from_forgery except: :sample

  def sample
   render json: @hogehoge
  end
end

You can to process requests from outside without any problems.

Sebastián Palma
  • 32,692
  • 6
  • 40
  • 59
Ryosuke Hujisawa
  • 2,682
  • 1
  • 17
  • 18
5

If you only want to skip CSRF protection for one or more controller actions (instead of the entire controller), try this

skip_before_action :verify_authenticity_token, only [:webhook, :index, :create]

Where [:webhook, :index, :create] will skip the check for those 3 actions, but you can change to whichever you want to skip

stevec
  • 41,291
  • 27
  • 223
  • 311
0

The simplest solution for the problem is do standard things in your controller or you can directely put it into ApplicationController:

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception, prepend: true
end
Sebastián Palma
  • 32,692
  • 6
  • 40
  • 59
Arish Khan
  • 254
  • 4
  • 10
-2

in Rails 6, I found a easy way to solve this "Can't verify CSRF token authenticity". Just puts

config.action_controller.default_protect_from_forgery = false # unless ENV["RAILS_ENV"] == "production"

in application.rb

its' convenient in development mode. but not use this in production.

dayudodo
  • 463
  • 4
  • 7