Devise: rememberable means that last_sign_in_at is not updated by trackable
Asked Answered
G

5

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?

Geometry answered 9/1, 2012 at 13:6 Comment(0)
K
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.

Kasper answered 9/1, 2012 at 20:12 Comment(4)
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.Rum
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.Kasper
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 }Rum
This is also applicable if you're using the saml_authenticatable module, which does not update tracked fields correctly.Barfield
H
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
Heads answered 18/8, 2012 at 19:23 Comment(2)
Is this the best solution yet?Kislev
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.Kassel
K
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.

Kasper answered 9/1, 2012 at 20:12 Comment(4)
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.Rum
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.Kasper
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 }Rum
This is also applicable if you're using the saml_authenticatable module, which does not update tracked fields correctly.Barfield
T
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
Trictrac answered 23/2, 2016 at 17:52 Comment(0)
H
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

Hern answered 24/5, 2018 at 10:4 Comment(2)
Can you provide some comment or explanation?Opera
Updated the answerHern
D
-1

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

Derail answered 23/1, 2015 at 12:8 Comment(1)
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).Magically

© 2022 - 2024 — McMap. All rights reserved.