Rails api gem and devise token authentication

randika picture randika · Jun 3, 2013 · Viewed 9.8k times · Source

If someone could shed some light It would be greatly appreciated as I'm bit clueless on this issue. Basically I'm trying to get the devise authentication done with a JSON only rails rest api app built on top of rails-api gem.

I've already implemented the Sessions and Registrations handling as described below.

class ApplicationController < ActionController::API
    include ActionController::MimeResponds
end

sessions_controller.rb

  class SessionsController < Devise::SessionsController
    prepend_before_filter :require_no_authentication, :only => [:create ]

    before_filter :ensure_params_exist
    def create
      build_resource
      resource = User.find_for_database_authentication(:email => params[:user][:email])
      return invalid_login_attempt unless resource

      if resource.valid_password?(params[:user][:password])
        sign_in("user", resource)
        render :json=> {:success=>true, :auth_token=>resource.authentication_token, :email=>resource.email}
      return
      end
      invalid_login_attempt
    end

    def destroy
      sign_out(resource_name)
    end

    protected

    def ensure_params_exist
      return unless params[:user][:email].blank?
      render :json=>{:success=>false, :message=>"missing login email parameter"}, :status=>422
    end

    def invalid_login_attempt
      warden.custom_failure!
      render :json=> {:success=>false, :message=>"Error with your login or password"}, :status=>401
    end

  end

registrations_controller.rb

  class RegistrationsController < Devise::RegistrationsController
    skip_before_filter :verify_authenticity_token, :only => :create

    def create
      user = User.new(params[:user])
      if user.save
        render :json=> {:user => [:email => user.email, :auth_token => user.authentication_token]}, :status => 201
      return
      else
        warden.custom_failure!
        render :json=> user.errors, :status=>422
      end
    end
  end

Everything works fine so far. In my other controllers I've added this line to secure them. before_filter :authenticate_user! but I get this error when calling with auth_token. ?auth_token=xxxxxxxxxxx

undefined method authenticate_user!

controllers
class RestaurantsController < ApplicationController

        before_filter :authenticate_user!

      def index
        @restaurants =  Restaurant.all

      end

      def show
        @restaurant = Restaurant.find(params[:id])

      end
    end

Still not sure what might be the issue here - I'm assuming this is happening because something is missing to work devise properly as its using ActionController::API

UPDATE It seems the issue is with my routes.rb itself - This is how the routes are done.

require 'api_constraints'

MyApi::Application.routes.draw do

  namespace :api, defaults: {format: 'json'} do
    scope module: :v1, constraints: ApiConstraints.new(version: 1,default: true) do
      devise_for :users
      resources :friends
      resources :locations
      resources :profiles
      resources :users


    end

    scope module: :v2, constraints: ApiConstraints.new(version: 2) do
    # Future releases of the API can go here
    end

  end

end

Now If repeat the devise_for :users outside the scope everything started working.

require 'api_constraints'

MyApi::Application.routes.draw do

  namespace :api, defaults: {format: 'json'} do
    scope module: :v1, constraints: ApiConstraints.new(version: 1,default: true) do
      devise_for :users
      resources :friends
      resources :locations
      resources :profiles
      resources :users


    end

    scope module: :v2, constraints: ApiConstraints.new(version: 2) do
    # Future releases of the API can go here
    end

  end
devise_for :users
end

Does any one has an explanation why?

Answer

Emil picture Emil · Aug 16, 2013

Step 1. Override devise controllers with custom controllers that replace redirects with JSON responses. Here's a custom SessionController that uses JSON responses .

Step 2. If an authentication error occurs, the control goes to warden which uses a failure_app which is nothing but a Rack application which sets the flash messages and renders/redirects based on the requested format. You need a custom json response with an errors key holding an array of errors instead of the default error key. So you need a custom_auth_failure_app.rb under config/initializers with this content.

Step 4. Now we have to tell Devise to use the custom failure app by adding this to config/initializers/devise.rb :

config.warden do |manager|
  manager.failure_app = CustomAuthFailure
end

Step 5. Enable token_authenticatable in the devise model and add the following to config/initializers/devise.rb

config.http_authenticatable = true
config.skip_session_storage = [:http_auth, :token_auth]

Step 6. If you are using a true JSON API which doesn't use session, use a version of warden ( > 1.2.2 ) which has patches to handle a nil session.

Also read my blog post about creating a tested, documented and versioned JSON API using Rails4 + Rails-API + Devise which talks about all these steps.