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
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
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!
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?
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.