13

I have an RoR app using Devise for logins. There is some code that is executed when a new User record is created, by being put in the user.rb file as an after_create call/macro/whatever. I need to make this code run after each login, instead of running on new user creation.

With some Googling, it seems that one option is to place Warden callbacks in the devise.rb code. My questions are:

  1. Is this right, and/or is there a better way to do this?
  2. If this is the right approach ...
    • Should the Warden::Manager... method defs go in devise.rb inside of Devise.setup, or after it?
    • Is after_authentication the callback I should use? I'm just checking to see if a directory based on the user's name exists, and if not, creating it.
whognu
  • 439
  • 1
  • 5
  • 15
  • 1
    I stuck the code in the Devise.setup block of devise.rb, and it seems to work. I remain open to other approaches. – whognu Jan 21 '14 at 22:07
  • If you fetch struggle work with properly in case of combined Omniauth and Database login modules in Devise you can see this post http://stackoverflow.com/questions/4753730/can-i-execute-custom-actions-after-successful-sign-in-with-devise that help me better – Engr. Hasanuzzaman Sumon Oct 07 '15 at 04:19

4 Answers4

26

Just subclass Devise's sessions controller and put your custom behaviour there:

# config/routes.rb
devise_for :users, :controllers => { :sessions => "custom_sessions" }

And then create your controller like this:

# app/controllers/custom_sessions_controller.rb
class CustomSessionsController < Devise::SessionsController
  ## for rails 5+, use before_action, after_action
  before_filter :before_login, :only => :create
  after_filter :after_login, :only => :create

  def before_login
  end

  def after_login
  end
end
Milind
  • 4,535
  • 2
  • 26
  • 58
Ashitaka
  • 19,028
  • 6
  • 54
  • 69
  • We don't seem to have a Devise::SessionController class anywhere in our code. Is this available from the gem? Would the CustomSessionsController just be in a new file in the controllers directory? – whognu Jan 22 '14 at 14:45
  • 1
    Yes to both questions. That's the controller used internally by Devise, as you can see here: https://github.com/plataformatec/devise/blob/master/app/controllers/devise/sessions_controller.rb – Ashitaka Jan 22 '14 at 18:37
  • 1
    Btw, I would name that new controller just SessionsController and not CustomSessionsController. The only reason I used this name is because if I had used the name SessionsController, you probably wouldn't have understood this line: `devise_for :users, :controllers => { :sessions => "sessions" }`. `:sessions => "sessions"` just looks weird :) – Ashitaka Jan 22 '14 at 18:39
  • 5
    This is the best answer. #after_sign_in_path_for should not be used as an arbitrary post signin callback as it can be used just to get the after sign in path at any point in time. Also, devise provides a ["after_database_authentication"](http://www.rubydoc.info/github/plataformatec/devise/master/Devise/Models/DatabaseAuthenticatable#after_database_authentication-instance_method) on the model but @Ashitaka's response keep it appropriately at the controller level. Although, Ashitaka, it would be great if you could include code to detect succesfull login state or not – Peter P. Jan 18 '15 at 02:59
  • 3
    Actually, it looks like if login is unsuccessful, the after_login will not be run, presumably due to a 401 warden exception or something. So after_login will only run if successful login – Peter P. Jan 18 '15 at 03:06
  • This works nicely with users in the database; however, the after_login won't get called when an OmniAuth user logs in. Any suggestion? – Hoang Huynh Feb 15 '15 at 16:29
  • 1
    Sure. Move the filter methods to your ApplicationController and then add `before_filter`s to both CustomSessionsController and OmniauthController. – Ashitaka Feb 16 '15 at 00:30
  • 1
    for Rails 5+ -> after_action :after_login, :only => :create – Milind Apr 09 '21 at 15:56
9

I found that using the Devise Warden hook cleanly allowed after login event trapping by looking for the ":set_user" :event.

In user.rb:

class User < ApplicationRecord
  Warden::Manager.after_set_user do |user, auth, opts|
    if (opts[:scope] == :user && opts[:event] == :set_user)
      # < Do your after login work here >
    end
  end
end
Mike Lapinskas
  • 149
  • 2
  • 5
1

I think this is an duplicate question. Yes you can execute code after every successful log in. you could write the code in your ApplicationController. Also have a look at http://github.com/plataformatec/devise/wiki/How-To:-Redirect-to-a-specific-page-on-successful-sign-in. Also, check out How to redirect to a specific page on successful sign up using rails devise gem? for some ideas.

You can do something like:

def after_sign_in_path_for(resource_or_scope)
Your Code Here
end

Reference Can I execute custom actions after successful sign in with Devise?

You could also inherit from devise session's class and use after_filter for logins.

Community
  • 1
  • 1
Saurabh
  • 1,086
  • 1
  • 15
  • 27
  • 1
    Weird - the other links I found suggested putting it into devise.rb. Since I don't want to conflate this with routing, I'm hesitant to use after_sign_in_path_for. – whognu Jan 21 '14 at 21:30
1

UPDATED 2022

Warden now has built-in callbacks for executing your own code on after_authentication:

Warden::Manager.after_authentication do |user, _auth, _opts| 
  TelegramService.send("#{account.name} just logged in") 
end

Source: https://github.com/wardencommunity/warden/wiki/Callbacks#after_authentication

Flavio Wuensche
  • 9,460
  • 1
  • 57
  • 54