Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails api gem and devise token authentication

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?

like image 870
randika Avatar asked Jun 03 '13 16:06

randika


2 Answers

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.

like image 183
Emil Avatar answered Oct 12 '22 18:10

Emil


For anyone having this same issue where the devise helper methods like authenticate_user are not working when using devise_token_auth and rails-api, try adding this line to app/controllers/application_controller.rb:

include DeviseTokenAuth::Concerns::SetUserByToken

When you run the devise_token_auth installation, it is supposed to automatically create this concern in the ApplicationController. This concern gives access to the helper methods like authenticate_user. Problem is, that concern doesn't get added if you're using rails-api instead of vanilla rails. So, you have to add it manually.

Not sure the root cause, but I suspect its because rails-api's ApplicationController inherits from ActionController::API, which is different from vanilla rails' ApplicationController, which inherits from ActionController::Base.

like image 24
Aaron Gray Avatar answered Oct 12 '22 20:10

Aaron Gray