Devise Ajax login: 'sessions#create' only

2019-04-10 10:56发布

问题:

My goal is to implement an Ajax login solution with Devise with the minimal change of patterns. I already reached it partially, but there is one problem with the failure callback. Let me explain the scenario:

  1. Display login form in a modal with remote: true Rails/UJS convention (OK);
  2. Create a custom sessions controller and point Devise to it (OK);
  3. Create a JS view file create.js.erb to respond for sessions#create action (PROBLEM);

The problem: My create.js.erb only contains an alert("Test ok"). When I submit the sessions#new form with the correct credentials the file create.js.erb is executed, the alert is shown. But with wrong credentials it doesn't, returning 401 Unauthorized status and create.js.erb is ignored.

Maybe someone know a quick trick to make create.js.erb run when login fail. This way I don't need to create a standalone Ajax script or change entire sessions controller.

Thank you,

The environment:

VERSIONS:
Rails 4.0.2
Devise 3.2.2

Custom sessions controller:

class Website::SessionsController < ::Devise::SessionsController
  respond_to :js # without it neither on success create.js.erb runs
  layout false   # action `new` pure html which is rendered in a modal box
end

sessions/create.js.erb

alert("Test ok");

Server log when login fails:

Started POST "/users/sign_in" for 127.0.0.1 at 2014-03-27 09:59:47 -0300
Processing by Website::SessionsController#create as JS
  Parameters: {"utf8"=>"✓", "user"=>{"email"=>"", "password"=>"[FILTERED]"}, "commit"=>"Fazer login"}
Completed 401 Unauthorized in 1ms

回答1:

Problem occurs because of:

warden.authenticate!(...)

It raise the exception and don't render your view after.

You should to override SessionsController#create action for your own behavior. For example like this:

def create
  self.resource = warden.authenticate(auth_options)
  if resource && resource.active_for_authentication?
    ...
    sign_in(resource_name, resource)
    ...
  else
    ...
  end
end


回答2:

When successful, the create.js.erb template is rendered.

When unsuccessful, for any reason, the new.js.erb template is rendered.

The easiest way to deal with this, is to simply add new.js.erb. This involves the least amount of modification to the devise controller.


It may be worth noting that I had to make the following adjustments, in addition to adding remote: true to the form, to even get the create.js.erb to render.

config/initializers/devise:

# If http headers should be returned for AJAX requests. True by default.
config.http_authenticatable_on_xhr = false

config/routes.rb:

devise_for :users, controllers: { sessions: 'users/sessions' }

controllers/users/sessions_controller.rb:

class Users::SessionsController < Devise::SessionsController

  respond_to :html, :js

end