64

All the questions I've found are related to a successful login with the helper after_sign_in_path_for(resource)

I have a login form in the index of the site, and when the login fails it redirects to "users/sign_in"

But how can I redirect to my site#index when the login fails?

Jai Chauhan
  • 4,035
  • 3
  • 36
  • 62
Juanjo
  • 773
  • 1
  • 6
  • 6

5 Answers5

102
  1. Create a custom_failure.rb in your lib directory, with:

     class CustomFailure < Devise::FailureApp
       def redirect_url
         your_path
       end
    
       def respond
         if http_auth?
           http_auth
         else
           redirect
         end
       end
     end
    
  2. In your Devise initializer, include:

       config.warden do |manager|
         manager.failure_app = CustomFailure
       end
    
  3. Make sure Rails is loading your lib files, in your application.rb :

     config.autoload_paths += %W(#{config.root}/lib)
    

Don't forget to restart your server.

I don't think there's an easier way to do this.

starball
  • 20,030
  • 7
  • 43
  • 238
Marco Antonio
  • 1,359
  • 1
  • 9
  • 8
  • 2
    This didn't work. I know it's the stock answer from the devise wiki. – dsaronin Nov 25 '11 at 08:53
  • The contents of the `respond` method can be refactored to `http_auth? ? http_auth : redirect`. Works great! – RyanScottLewis Jul 02 '12 at 17:38
  • 3
    This doesn't work for me, it still redirects back to the login – Chris Edwards Jan 16 '13 at 15:48
  • 2
    This worked for me using Devise 3.2.4. Make sure when you include or change any of these files that you restart the server. You'll probably get an error like this: `NameError - undefined local variable or method 'login' for #`if you don't. – Omni Jul 09 '14 at 03:24
  • It works for my but only for a session. How would I go about for a registration? – Code-MonKy Aug 19 '17 at 11:21
  • 2
    Works great in Rails 5 with Devise 4.3.0. Just put the file custom_failure.rb in /app/models/concerns and skip the autoload_paths step #3. Thanks @Marcao, you saved my time! – Fernando Kosh Mar 06 '18 at 00:21
15

If you use your own SessionsController, you can re-assign the :recall value of auth_options to recall the controller#method you want before running warden.authenticate!(auth_options), for example:

in app/controllers/users/sessions_controller.rb

class Users::SessionsController < Devise::SessionsController
  #...
  def create
    #...
    auth_options = { :recall => 'site#index', :scope => :user }
    resource = warden.authenticate!(auth_options)
    #...
  end
  #...
end

With this way, you don't need to create the customized FailureApp and modify the configs.

Sibevin Wang
  • 4,480
  • 3
  • 27
  • 27
3

This is what happens with devise 3.1.0

Started POST "/users/sign_in"
Processing by Devise::SessionsController#create
Completed 401 Unauthorized
Processing by Devise::SessionsController#new

new gets called because of the auth_options defined at the end of gems/devise-3.1.0/app/controllers/devise/sessions_controller.rb

You should redefine the auth_options used in the create action. I copied the controller in app/controllers/devise/sessions_controller.rb of my Rails application and replaced the auth_options method like this

def auth_options
  { :scope => resource_name, :recall => "Home#new" }
end

It does the trick, but the url is still /users/sign_in

I'll try to fix that as well.

pmontrasio
  • 541
  • 6
  • 10
  • I'm on devise 3.2.2 now. The solution of Marcao works perfectly. No need to copy and patch devise controllers or set auth_options. – pmontrasio Feb 14 '14 at 17:12
1

Elaborating on Marcao's answer, I highly recommend placing some debugger in your CustomFailure respond method in order to better understand what is going on.

Class CustomFailure < Devise::FailureApp
  def respond
    binding.pry
    super
  end
end

If you look at the FailureApp Devise Source Code for the respond method it is super easy to understand what is going on.

def respond
  if http_auth?
    http_auth
  elsif warden_options[:recall]
    recall
  else
    redirect
  end
end

So for example in order to return a redirect_url you would want to make sure that your respond code conditionals eventually return redirect.

However if you want to maybe return a standard 401 status as defined in the http_auth method, you want to verify that your respond method code returns http_auth.

Thus it is worth your while to look into the definition of the http_auth? In particular, note the: request.xhr? method, which will return 0 for json requests (recall that 0 actually evaluates to true in ruby)

def http_auth?
  if request.xhr?
    Devise.http_authenticatable_on_xhr
  else
    !(request_format && is_navigational_format?)
  end
end

And maybe check your initializers/devise file for config.http_authenticatable_on_xhr or config.navigational_formats in order to control the response that you want. This configuration can really affect what Devise returns and can often lead to unexpected behavior due to what it does here under the hood.

Viktor
  • 2,982
  • 27
  • 32
AmitF
  • 1,287
  • 13
  • 9
1

You can change the default sign_in path.

Check out https://github.com/plataformatec/devise/wiki/How-To:-Change-the-default-sign_in-and-sign_out-routes

MikeH
  • 796
  • 7
  • 18
  • Thanks @MikeH, I tried this. **devise_for :users do get 'users', :to => 'site#index', :as => :user_root # Rails 3 end** Works perfect redirecting to my index for all cases, except when a login fails. In this case it redirect to user/sign_in, and I want to be redirected to "site#index". – Juanjo Apr 29 '11 at 22:17
  • Hmm. When the login fails, devise's failure app redirects to **new_#{scope}_session_path** (new_user_session_path in your case). When you do **rake routes**, what controller/action is shown for this resource path? – MikeH Apr 30 '11 at 00:43
  • 4
    Did you find an answer ? I am still looking for it… – ronnieonrails Jul 19 '11 at 15:12