Extending Devise SessionsController to authenticate using JSON

I am trying to build a rails API for an iphone app. Devise works fine for logins through the web interface but I need to be able to create and destroy sessions using REST API and I want to use JSON instead of having to do a POST on the sessions controller and having to parse the HTML and deal with a redirect.

I thought I could do something like this:

class Api::V1::SessionsController < Devise::SessionsController  
  def create
    super
  end  
  def destroy
    super
  end  
end

and in config/routes.rb I added:

namespace :api do
  namespace :v1 do
    resources :sessions, :only => [:create, :destroy]
  end
end

rake routes shows the routes are setup properly:

   api_v1_sessions POST   /api/v1/sessions(.:format)     {:action=>"create", :controller=>"api/v1/sessions"}
    api_v1_session DELETE /api/v1/sessions/:id(.:format) {:action=>"destroy", :controller=>"api/v1/sessions"}

When I POST to /user/sessions everything works fine. I get some HTML and a 302.

Now if I POST to /api/v1/sessions I get:

Unknown action AbstractController::ActionNotFound

curl -v -H 'Content-Type: application/json' -H 'Accept: application/json'   -X POST http://localhost:3000/api/v1/sessions   -d "{'user' : { 'login' : 'test', 'password' : 'foobar'}}"

Solution 1:

This is what finally worked.

class Api::V1::SessionsController < Devise::SessionsController  
  def create  
    respond_to do |format|  
      format.html { super }  
      format.json {  
        warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#new")  
        render :status => 200, :json => { :error => "Success" }  
      }  
    end  
  end  
  def destroy  
    super  
  end  
end  

Also change routes.rb, remember the order is important.

devise_for :users, :controllers => { :sessions => "api/v1/sessions" }
devise_scope :user do
  namespace :api do
    namespace :v1 do
      resources :sessions, :only => [:create, :destroy]
    end
  end
end

resources :users

Solution 2:

I ended up using a combination of @akshay's answer and @mm2001's answer.

class Api::SessionsController < Devise::SessionsController
  def create
    warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#failure")
    render :json => {:success => true}
  end

  def destroy
    Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)
    render :json => {}
  end

  def failure
    render :json => {:success => false, :errors => ["Login Failed"]}
  end
end

... and in the devise initializer, I had to do this to get the #create method to use my :recall handler

# config/initializers/devise.rb
config.navigational_formats = [:"*/*", "*/*", :html, :json]

This is with Devise 1.5.1 and Rails 3.1.

Solution 3:

A recent answer here: http://jessehowarth.com/devise has some more detail (per Using Devise 1.3 to authenticate JSON login requests)

Solution 4:

I solved the problem by creating a small service that dispenses authentication tokens. I wrote a blog post about it: http://matteomelani.wordpress.com/2011/10/17/authentication-for-mobile-devices/. You can also get the code here: https://github.com/matteomelani/Auth-Token-Service-Prototype.