Ruby on Rails Devise Timeoutable redirect after inactivity
Asked Answered
M

2

6

I am using the Timeoutable feature of Devise, and I am a little confused. If the user's session times out, they are redirected to the login page when they refresh the page or perform an action that involves a Rails controller (ex. navigating to another page within the site). However, due to the user-sensitive information on my site, I would like to automatically send the user to the login screen immediately after the timeout has occurred. Is there a standard way of doing this?

Here is my code for reference: https://mcmap.net/q/1769786/-extending-devise-to-use-remote-login

Mejia answered 1/4, 2016 at 14:31 Comment(0)
M
15

I ended up finding the answer to my own question, with the help of @Sergio Tulentsev, who gave me some helpful information in the comments of this Stack Overflow answer: https://mcmap.net/q/544976/-automatic-logout-after-inactivity-idle

Basically, the Timeoutable feature of Devise will expire the authentication token every 30 minutes by default...or you can configure it in config/intializers/devise.rb to whatever time you want:

# ==> Configuration for :timeoutable
# The time you want to timeout the user session without activity. After this
# time the user will be asked for credentials again. Default is 30 minutes.
config.timeout_in = 30.minutes

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.

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.

Mejia answered 6/4, 2016 at 18:53 Comment(3)
Hi and thanks a lot! i have a question - you said it replaces the Devise timeoutable functionality, but this is only on JS, so user can bypass it. shouldnt we have it on both client and server?Unyoke
@Unyoke That's a really good point. I hadn't thought of that. It might not be a bad idea, then, to use auto_session_timeout in addition to Devise's timeoutable function. It's been a while since I looked at this code, but if I remember correctly, there are not any incompatibility issues between the two. I think I just suggested removing Devise's timeoutable ability to promote cleaner code.Mejia
ok thanks! i have the devise functionality and ill add your suggestion for a better user experienceUnyoke
C
-1

I prefer avoid use of not essential gems

  • Enable timeoutable in your devise model (in my case app/model/user.rb)
  • Create config/initializer/custom_failure_app.rb
class CustomFailureApp < Devise::FailureApp 
  def redirect_url 
    if request.xhr? 
      send(:"new_#{scope}_session_path", :format => :js)
    else
      super
    end
  end
end
  • Edit config/initializer/devise.rb
config.timeout_in = 30.minutes
config.http_authenticatable = false
config.http_authenticatable_on_xhr = false
config.navigational_formats = ["*/*", :html, :js]

config.warden do |manager|
  manager.failure_app = CustomFailureApp
end
  • Create view/devise/sessions/new.js.erb
 window.location = "/"

Usually this is enough but the new action in devise sessions_controller use respond_with that is not able to manage :js. So we create a new controller with multiple format and override session new

  • Create app/controllers/sessions_controller.erb
class SessionsController < Devise::SessionsController
  def new
      self.resource = resource_class.new(sign_in_params)
      clean_up_passwords(resource)
      yield resource if block_given?
      respond_to do |format|
        format.js
        format.html
      end
  end
end
  • Edit config/routes.rb [Evaluate the use of path_prefix based on your configuration]
devise_for :users, :path_prefix => 'd', controllers: { sessions: 'sessions' }

Chemoprophylaxis answered 6/11, 2019 at 15:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.