automatic logout after inactivity/idle
Asked Answered
S

4

24

How to set up in the rails application that if any user is idle for 30 minutes or a specific period of time he should be automatically get logged out. Can any one give any solution . i am using devise for authentication purpose. Any help is appreciated .

Sola answered 29/1, 2013 at 7:41 Comment(1)
Please see #5233017Brecher
P
29

You should use Timeoutable model trait.

Timeoutable takes care of veryfing whether a user session has already expired or not. When a session expires after the configured time, the user will be asked for credentials again, it means, he/she will be redirected to the sign in page.

Options

Timeoutable adds the following options to devise_for:

  • +timeout_in+: the interval to timeout the user session without activity.

In your model you need

devise :timeoutable
# along with :database_authenticatable, :registerable and other things.

Also, take a look at config/initializers/devise.rb, you can configure timeout value there.

Peltz answered 29/1, 2013 at 7:42 Comment(6)
Thanks for the reply but I also need to show the alert message that the user is idle for such period of time , kindly monitor your activity just before the logging him out from the application.Sola
I would give an upvote to this answer if it were to tell me where this code goes, and give me an example of what it looks like. =)Nerval
@alex0112: as you wish :)Peltz
The problem I ran into with Timeoutable is that it does not automatically log the user out. That is, the user has to perform an action before Devise checks if the user is still logged in. For instance, if the user times out, they will still see the current page until they refresh the page or perform an action that hits a Rails controller. Is there any way to get around this? I have user-sensitive information that I don't want displayed on the page indefinitely until a user performs an action again.Comehither
@kales33: well yes, after user has loaded a page, you can't affect it from the server. The first thing that comes to mind is polling with javascript, but this would probably extend the timeout indefinitely. How about meta refresh tag with some big value (equal to your devise timeout setting)?Peltz
@SergioTulentsev Thank you for the explanation and for the idea! I looked all over just for the explanation you gave me, and I couldn't find it. So thank you!Comehither
C
18

I know this question has already been answered, but I thought I would provide my solution as well, since in my case I was looking for more functionality than even Devise's timeoutable feature could provide. A big thanks to @Sergio Tulentsev for providing me with helpful explanations and ideas in the comments section of his answer!

Problem

Because Devise runs on the server side and not the client side, when the authentication token times out, the client is not aware of the timeout until the user performs an action that calls a Rails controller. This means that the user is not redirected to the login page on timeout until they perform an action that calls a Rails controller.

In my case, this was a problem because my web page contained user-sensitive information that I did not want displayed indefinitely if the user forgot to logout and no action was performed on the page.

Solution

I installed the gem auto-session-timeout, which adds code to the client side to periodically check if the authentication token has expired.

Dependencies

It doesn't say in the ReadMe, but auto-session-timeout requires jquery-periodicalupdater in order to work. This page contains the reason why: auto-session-timeout periodicalupdater explanation

Configuration

Here are the steps I took in order to get auto-session-timeout to work with Devise:

  1. First of all, I followed the steps here in order to customize the Devise sessions controllers. Just for reference, my config/routes.rb file is set up in the following way:

    Myapp::Application.routes.draw do
      devise_for :users, controllers: { sessions: "users/sessions" }
      #other routes
    end
    
  2. In app/controllers/users/sessions_controller.rb, I have the following code:

    class Users::SessionsController < Devise::SessionsController
      layout 'login' #specifies that the template app/views/layouts/login.html.erb should be used instead of app/views/layouts/application.html.erb for the login page
    
      #configure auto_session_timeout
      def active
        render_session_status
      end
    
      def timeout
        flash[:notice] = "Your session has timed out."
        redirect_to "/users/sign_in"
      end
    end
    
  3. In app/controllers/application_controller.rb, I have the following code:

    class ApplicationController < ActionController::Base
      # Prevent CSRF attacks by raising an exception.
      # For APIs, you may want to use :null_session instead.
      protect_from_forgery with: :exception
      before_action :authenticate_user!
      auto_session_timeout 30.minutes
    
    end
    

    Note that we set the authentication token expiration time to be 30 minutes using auto_session_timeout. This replaces the Devise timeoutable functionality.

  4. In my app, I have two layout templates - one for the all the pages that the user sees when they are logged in (app/views/layouts/application.html.erb), and one just for the login screen (app/views/layouts/login.html.erb). In both of these files, I added the line below within the html <body> element:

    <%= auto_session_timeout_js %>
    

    This code will generate Javascript that checks the status of the authentication token every 60 seconds (this time interval is configurable). If the token has timed out, the Javascript code will call the timeout method in the app/controllers/users/sessions_controller.rb file.

    Note that I have included this code on the app/views/layouts/login.html.erb page. The reason for this is because if there is no activity on the login page for more than 30 minutes (or whatever the auto_session_timeout setting is in the application_controller.rb file), then the authentication token will expire, and the user will receive an Invalid Authentication Token error when trying to login. Adding the code <%= auto_session_timeout_js %> will cause the login to be refreshed when the authentication token expires, thus preventing this error from occurring.

Comehither answered 7/4, 2016 at 16:39 Comment(10)
Nice...! This has been asked long back and already resolved, though your answer is pretty nice to have the client side session timeout as well. +1Sola
What if user closes the browser or shuts down machine?Fathead
@Fathead The auto-session-timeout gem uses the cookie _myapp_session to track the user's session. Whenever the cookie expires or is cleared, the session is no longer valid. In terms of the cookie expiring, the expiration time is specified in the application_controller.rb file. In terms of the cookie being cleared, this is based on your browser settings. The cookie is set to be cleared when the browsing session ends. From what I have read, this is different for each browser. See https://mcmap.net/q/17339/-when-does-a-cookie-with-expiration-time-39-at-end-of-session-39-expire for some examples.Comehither
@Fathead One last note (it wouldn't fit in my last comment)...The browsing session will definitely end when the computer is shut down.Comehither
Tried this on a rails 5 project. This gem doesn't work in rails 5.Tipsy
@Tipsy do you have an update regarding rails 5 ? thanksDoherty
@dowi, joeyk16 I haven't used this gem/strategy with Rails 5. From my understanding, there were significant changes from Rails 4 to Rails 5, so it doesn't surprise me that this gem does not work in Rails 5. I'm sorry, but due to my inexperience with Rails 5, I don't know of a workaround.Comehither
Is this the only working javascript timeout with devise? Was trying to come up with my own ping, but that was keeping the connection alive.Wolfenbarger
@Wolfenbarger It was the only one I could find at the time I was working on this projectComehither
The gem works like a charm! thanks! But is there any way to get and update the user before the user is redirected to login page in timeout action?Milka
S
7

Using Devise Gem:

We can use inbuilt feature of the devise gem but it will not automatically redirect to the sign in page after timeout, redirection will be done after we perform any action.

We can Perform automatically sign out:

By using gem "auto-session-timeout"

https://github.com/pelargir/auto-session-timeout

The disadvantage of using this gem is that it will logout automatically if user is only typing(performing key press event) till the timeout time.

we can override the disadvantage by using Javascript:

Step 1: Define the routes

get 'application/session_time'

Step 2: JavaScript will contain

$(document).ready(function(){
if($("#user_logged").is(":visible") == true )
{
    $(document).on( "keypress keydown", function () {
        console.log("hello");
        $.ajax({
            type:"GET",
            url:"/application/session_time",
            dataType:"html",
        });
    });
}
});

Step 3: application controller will contain:

@session_time = 5.minute
auto_session_timeout @session_time
def session_time
  @session_time = 5.minute
end

Step 4: div to find it is sign in page or not

<% if user_signed_in? %>
  <div id="user_logged"></div>
<% end %>

The blank div is kept because we have to load JavaScript only when user is logged in ,so instead of finding current user is nil or not.

I have done with the blank div,it will be available if user is loggedin,so in beginning of JavaScript it is checked that div_id "user_loged" is present or not.

Sentient answered 18/4, 2017 at 13:39 Comment(0)
H
2

There's a simple way of dealing with this that hasn't been mentioned which requires no extra gems or dependencies.

Say in initializers/devise.rb you've set config.timeout_in = 30.minutes and added :timeoutable to your model. Trigger the following javascript on page loads when a user is logged in:

setAccurateTimeout(() => {
  window.location.reload();
}, 30 * 60 * 1000);  // minutes (from devise setting) * sec * ms

function setAccurateTimeout(callback, length) { 
  // adjust any discrepencies every 5s
  let speed = 5000,                
      steps = length / speed,                           
      count = 0,
      start = new Date().getTime();

  function instance() {
    if (count++ == steps) {
      callback();
    } else {
      // console.log(`step ${count} of ${steps}, time passed ${count * speed}ms of ${length}ms`)
      let diff = (new Date().getTime() - start) - (count * speed);
      // console.log(`accuracy diff ${diff}ms, adjusted interval: ${speed - diff}ms`);
      window.setTimeout(instance, (speed - diff));
    }
  }
  window.setTimeout(instance, speed);
}

A regular setTimeout could probably be used, even though over time it introduces inaccuracies due to CPU usage. It would likely just trigger the logout reload slightly later than intended.

The server will terminate the session slightly before this finishes due to being initialized prior to javascript on the client side. When the page reloads the browser will end up on the login screen. This method also makes it easy to trigger a warning modal in advance, for example at the 2 minute mark with a countdown showing the remaining seconds and a button which can be clicked to stay signed in.

Extra tip: on a "stay signed in" button, set the url to one of your pages and add the data-remote='true' attribute. When clicked this will fire off a request to the server without reloading the page the user is on, thus fulfilling the activity requirement and resetting devise's timeout without needing to reload or navigate anywhere. Cancel any programmatic page reload, then restart the main timeout.

Hotheaded answered 1/8, 2020 at 0:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.