24

I have an app that has basic Devise authentication. After sign in, I would like to look up the user account (user belongs_to account, account has_many users), and store that in the session so that it is available like the @current_user.

What is the rails way of storing session in formation like this? Is there a hook I can use with Devise to execute code after successful sign-in?

vfilby
  • 9,938
  • 9
  • 49
  • 62

5 Answers5

39

Actually, the accepted answer does not work properly in case of combined Omniauth and Database login modules in Devise.

The native hook that is executed after every successfull sign in action in Devise (disregarding the user authentication channel) is warden.set_user (called by devise sign_in helper: http://www.rubydoc.info/github/plataformatec/devise/Devise/Controllers/SignInOut#sign_in-instance_method).

In order to execute custom action after successfull user sign in (according to Warden Docs: https://github.com/hassox/warden/wiki/Callbacks), put this into initializer (eg. after_sign_in.rb in config/initializers)

Warden::Manager.after_set_user except: :fetch do |user, auth, opts|
  #your custom code
end

Update 2015-04-30: Thanks to @seanlinsley suggestion (see comments below), I have corrected the answer to include except: :fetch in order to trigger the callback only when user is authenticated and not every time it is set.

Update 2018-12-27 Thanks to @thesecretmaster for pointing out that Warden now has built-in callbacks for executing your own code on after_authentication https://github.com/wardencommunity/warden/wiki/Callbacks#after_authentication

MatFiz
  • 973
  • 1
  • 8
  • 25
  • 3
    It turns out `after_set_user` is called whenever the user is set, not whenever the user is authenticated, which happens at least once per request. It looks like `after_authentication` would be the more appropriate callback. – seanlinsley Apr 23 '15 at 20:36
  • 1
    Looks like for some reason Devise doesn't trigger `after_authentication`. Devise's own source code does `after_set_user except: :fetch` instead: https://github.com/plataformatec/devise/blob/66db52ce31b5d8629f5813a1d7f03a8bc17e5d52/lib/devise/hooks/lockable.rb#L3 – seanlinsley Apr 23 '15 at 21:44
  • Thank you @seanlinsley for the throught investigation! Indeed, the after_set_user except: :fetch works best! I have corrected the answer according to your findings. – MatFiz Apr 30 '15 at 12:01
  • 2
    Sounds good but feels akward to write this code in an initializer. Ain't there a way out of this in a `after_filter` from `Devise::RegistrationsController`, while being able to access `current_user`? – Augustin Riedinger Sep 14 '15 at 18:29
  • It seems like the official "right" answer would be to use [`Warder::Manager.after_authentication`](https://github.com/wardencommunity/warden/wiki/Callbacks#after_authentication) – thesecretmaster Dec 23 '18 at 16:59
  • Good point, @thesecretmaster! Those callbacks were not available back in 2014. I will update the answer to include a link to the Warden callback docs. Thx! – MatFiz Dec 27 '18 at 09:24
18

Edit: Please consider that this was once a good solution, but there are probably better ways of handling this. I am only leaving it here to give people another option and to preserve history, please do not downvote.

Yes, you can do this. The first resource I'd look at is 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)
  session[:my_account] = current_user.account
  profile_url
end

You can implement this method in your ApplicationController or in a custom RegistrationsController.

Community
  • 1
  • 1
Anthony Panozzo
  • 3,274
  • 1
  • 24
  • 22
  • 4
    [This](http://rubydoc.info/github/plataformatec/devise/master/Devise/Models/DatabaseAuthenticatable#after_database_authentication-instance_method) seems a less hacky way. – Anjan Dec 31 '14 at 09:47
  • This was answered in 2011 and was the accepted solution at the time. Thanks for the update on another method. – Anthony Panozzo Jan 09 '15 at 17:26
11

i'm using rails 5 and devise 4.2.1, my solution is overide devise function on user model:

def after_database_authentication
    # here's the custom code
end

and the user model will look like this:

class User < ApplicationRecord
    devise :database_authenticatable, :registerable,
        :recoverable, :rememberable, :trackable, :validatable,
        :timeoutable, :lockable
    def after_database_authentication
        # here's the custom code
    end
end

it was called just after the authentication, i read it from this devise documentation, hope this could help

Esgi Dendyanri
  • 497
  • 1
  • 8
  • 11
6

I resolved this problem by overriding the create method of the session controller like following

class Admin::SessionsController < Devise::SessionsController

   def create 
     super
     # here goes my code
     # my settings, etc 
     # do something with current_admin.fullname, for example
  end 
end

In other words, if authentication is successful (by calling super) then I perform my settings.

fade2black
  • 546
  • 1
  • 10
  • 26
  • This does not work as that controller action is expecting a page to be rendered as the last return – hummmingbear Oct 29 '18 at 16:40
  • @theartofbeing Please read the question in the OP carefully. It says "Is there a hook I can use with Devise to execute code after successful sign-in?" In other words the author wants to run some code after successful sign-in. The fact every controller needs a page to render is a very BASIC fact in Rails programming and I think the author is well aware of this fact. Also, why are you 100% sure that I don't have a template `create.js` or `create.html.erb`? Again, the issue is not about what to render or not. – fade2black Oct 29 '18 at 17:09
  • I guess I interpreted it as wanting to call a method when authentication/sign in is successful, and then continuing with the `create` method. In other words, I might want to update an attribute on the user after I **know** the sign in was successful, while still leaving the functionality of the devise method in-tact (i.e. rendering some template) – hummmingbear Nov 01 '18 at 04:25
0

In application controller, you can simply add an after action.

app/controllers/users/application_controller.rb

class ApplicationController < ActionController::Base
  after_action :do_something

  def do_something
    # do something
  end
end
Yannick Lescure
  • 109
  • 2
  • 3