25

I am using devise to sign up/in.

routes

get 'profile' => 'profile#get_profile'
post 'profile' => 'profile#create_profile'

and profile_controller

def get_profile
    render json: {user: current_user}, status: :ok
end

def create_profile
    render json: {user: current_user}, status: :ok
end

GET: http://localhost:3000/user/profile returns the expected output. However,

POST request throws an error saying:

ActionController::InvalidAuthenticityToken in User::ProfileController#create_profile.

Please demystify this behavior.

Imran Ahmad
  • 2,798
  • 3
  • 28
  • 49
  • Possible duplicate of [ActionController::InvalidAuthenticityToken](http://stackoverflow.com/questions/3364492/actioncontrollerinvalidauthenticitytoken) – NickGnd Dec 13 '15 at 14:28

3 Answers3

45

To disable CSRF protection you can edit your ApplicationControllerlike this:

class ApplicationController < ActionController::Base
  protect_from_forgery with: :null_session

  # ...
end

or disable the CSRF protection for specific controller:

class ProfilesController < ApplicationController
  skip_before_action :verify_authenticity_token

  # ...
end

:null_session strategy empties the session instead of raising an exception which is perfect for an API. Because the session is empty, you can't use current_user method or othes helpers that refer to the session.

IMPORTANT:

  • protect_from_forgery with: :null_session must be used only in specific cases, for example to allow API request (POST/PUT/PATCH/DELETE) without html form
  • With protect_from_forgery with: :null_session you must restrict access to your data with an authorization system because every one could do request against your API endpoint
  • Don't remove protect_from_forgery with: :exception for requests that are done through html form, is dangerous! (read here http://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf)

To handle both standard requests (through html form) and API requests generally you have to set up two different controller for the same resource. Example:

Routes

Rails.application.routes.draw do
  resources :profiles

  namespace :api do
    namespace :v1 do
      resources :profiles
    end
  end

end

ApplicationController

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception
end

ProfilesController

(standard controller for html requests)

# app/controllers/profiles_controller.rb
class ProfilesController < ApplicationController

  # POST yoursites.com/profiles
  def create
  end
end

Api::V1::ProfilesController

(controller for API requests)

# app/controllers/api/v1/profiles_controller.rb
module Api
  module V1
    class ProfilesController < ApplicationController
      # To allow only json request
      protect_from_forgery with: :null_session, if: Proc.new {|c| c.request.format.json? }

      # POST yoursites.com/api/v1/profiles
      def create
      end
    end
  end
end

refereces: http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection/ClassMethods.html#method-i-protect_from_forgery

NickGnd
  • 5,107
  • 1
  • 20
  • 26
  • I have disabled CSRF protection for now. When did protect_from_forgery with: :null_session I was unable get current_user from session. – Imran Ahmad Dec 13 '15 at 15:34
  • @Emmy `protect_from_forgery with: :null_session` provides an empty session during every request, or rather, you can't use `current_user` method or helpers that refer to the `session`, because it's empty. I edited my answer to try to explain better the different cases. – NickGnd Dec 13 '15 at 19:32
  • @Imran as NickGnd has outlined, and which I think needs to be highlighted: YOU NEED TO HAVE an authorisation and authorisation in place to ensure that rogue API post requests cannot do any damage to your app. – BenKoshy Jun 29 '18 at 05:58
5

Get requests don't have an authenticity token.

You will have to add the request forgery stuff to your forms using this

<%= csrf_meta_tag %> 

And address via javascript

$('meta[name="csrf-token"]')
Austio
  • 5,939
  • 20
  • 34
-3

In ApplicationController (or another controller your controllers inherit from) there's a line:

protect_from_forgery with: :exception

Remove it and CSRF checks will be disabled.

Jeiwan
  • 954
  • 6
  • 13