10

I have being using Devise, and relying on last_sign_in_at of the user model to work out if my customers have not returned within X days. However, I recently discovered that last_sign_in_at is only updated when an actual form log in event occurs, as opposed to when a user is logged in automatically due to the inclusion of rememberable.

If want to ensure that last_sign_in_at is updated each time a user logs in (a new browser session), regardless of whether they used a form to log in or were automatically logged in by the rememberable cookie, how would I go about doing this in a Devise-compatible way?

Matthew O'Riordan
  • 7,981
  • 4
  • 45
  • 59

5 Answers5

15

Taking Matthew's solution, I think the code should be the following (note the not-operator before the session[:logged_signin]):

before_filter :update_last_sign_in_at

protected

def update_last_sign_in_at
  if user_signed_in? && !session[:logged_signin]
    sign_in(current_user, :force => true)
    session[:logged_signin] = true
  end
end
Christoph Lupprich
  • 1,170
  • 8
  • 16
  • Is this the best solution yet? – Rafael Oliveira Sep 03 '14 at 14:25
  • Got it. You're using a custom cookie parameter to check whether this is a new or existing session. Thanks for the tip, this feels like a much cleaner approach in my case as it means I don't have to dig into Devise's internals. – Topher Hunt Feb 11 '15 at 16:48
6

The trackable hook is from Warden's after_set_user hook -- what you could do to easily remedy this is set a before_filter to call sign_in.

This could be optimized, but test to see if using

before_filter proc{ sign_in(current_user, :force => true) }

updates the last_signed_in_at timestamp.

RobH
  • 568
  • 5
  • 13
  • 1
    when does the before_filter get called? I am hoping not on each request, and only on before authentication for the first time that session? Also, stupid question, but where do I add before_filter? I tried adding it to the Devise user model and I got undefined method before_filter. – Matthew O'Riordan Jan 09 '12 at 23:49
  • That would be run on each request -- it was just a quick/dirty debug step to see if that would solve it before implementing a better solution. You should be able to add it to any controller, ApplicationController if you want it global. – RobH Jan 10 '12 at 00:23
  • 4
    Hi Rob, this is what I went for in the end which a) checks that a user is logged in before forcing a sign_in, and b) ensures that the sign_in (forced) method is only executed once per session. (excuse the ; instead of line breaks, comments don't allow line breaks) `before_filter proc { if user_signed_in? && session[:logged_signin]; sign_in(current_user, :force => true); session[:logged_signin] = true; end }` – Matthew O'Riordan Jan 10 '12 at 08:34
  • This is also applicable if you're using the `saml_authenticatable` module, which does not update tracked fields correctly. – Daniel Bonnell Nov 09 '16 at 18:34
2

Devise: rememberable means that last_sign_in_at is not updated by trackable

Expanding on previous solutions, the problem with them would be that if the user signs in normally, they will "sign in twice". Which will set last_sign_in_at to the same (or almost the same) value as current_sign_in_at. On my site, I use last_sign_in_at to let the user know what has happened since last time they visited the site, and as such I need it to be somewhat accurate. Also, it logs +1 login count.

Also, there are people (like myself) who leave a browser window open for days without closing it (and hence never clearing the session flag). For metric purposes etc, it can be useful if such user behavior sometimes refresh the current_sign_in_at time.

The below variants will remedy these things.

class ApplicationController < ActionController::Base
  before_filter :update_sign_in_at_periodically
  UPDATE_LOGIN_PERIOD = 10.hours

  protected

  def update_sign_in_at_periodically
    if !session[:last_login_update_at] or session[:last_login_update_at] < UPDATE_LOGIN_PERIOD.ago
      session[:last_login_update_at] = Time.now
      sign_in(current_user, :force => true) if user_signed_in?
    end
  end
end

However, when I try the above, using Devise 3.2.4, I do get a new login when it auto-logins by cookie (login-count +1 and current_sign_in_at being set). So, I'm left with only the issue of wanting the tracking to periodically update even for users which keep the session open.

class ApplicationController < ActionController::Base
  before_filter :update_sign_in_at_periodically
  UPDATE_LOGIN_PERIOD = 10.hours 

  protected 

  def update_sign_in_at_periodically
    # use session cookie to avoid hammering the database
    if !session[:last_login_update_at] or session[:last_login_update_at] < UPDATE_LOGIN_PERIOD.ago
      session[:last_login_update_at] = Time.now
      if user_signed_in? and current_user.current_sign_in_at < 1.minute.ago # prevents double logins
        sign_in(current_user, :force => true)
      end
    end
  end
end
Community
  • 1
  • 1
2

On the application_controller you can set a before_action that checks if the current_sign_in_at of the current user is longer then X ago. If it is, then use sign_in(current_user, force: true) that updates the current_sign_in_at.

before_action :update_last_sign_in_at

def update_last_sign_in_at
   return unless user_signed_in? && current_user.current_sign_in_at < 12.hours.ago
   sign_in(current_user, force: true)
end

I use it so to detect inactive users (not signed in for 6 months) and delete them. #GDPR

Jake
  • 21
  • 1
  • 5
-1

AFAIK you can also use update_tracked_fields! on that current_user model.

  • `update_tracked_fields!` takes the request as a parameter. Since devise also tracks IP. Just do this in the controller, `current_user.update_tracked_fields!(request)`. – Swaathi Kakarla Dec 14 '15 at 08:26