Rails 3 + Devise: log out user after timeout - ruby-on-rails

In my app I set Devise to timeout a session after 30 minutes. Which works fine... the user has to log in again after that time. My only problem is that devise apparently doesn't use the destroy action in the session controller after a session has timed out. So the attribute :signed_in for the user isn't set to 'false'.
So even after a session has timed out this user is still displayed as online.
Is there a way to destroy the session after the timeout or set the signed_in attribute to false after a certain time and on browser close?
My destroy action in session controller:
def destroy
current_user.try("signed_in=", false); current_user.save
signed_in = signed_in?(resource_name)
sign_out_and_redirect(resource_name)
set_flash_message :notice, :signed_out if signed_in
end

I'm not exactly an expert on Devise but I suspect this is because of the stateless nature of HTTP. Devise only knows a session is timed out when the user tries to access a page again after your timeout length and will likely only call the destroy method when a user actually logs out and the destroy method is called on the session controller.
In order to achieve what you are likely looking for, you would need to run a background process that sweeps for old sessions and then manually calls the destroy method (or does something similar).

With the new Devise version it works as follows to have an accurate online/offline status:
put this in your application_controller:
before_filter :last_request
def last_request
if user_signed_in?
if current_user.last_request_at < 1.minutes.ago
current_user.update_attribute(:last_request_at, Time.now)
end
if current_user.currently_signed_in = false
current_user.update_attribute(:currently_signed_in, true)
end
end
end
With every action the app checks if the last request was over 1min ago, if yes, he updates the user attribute.
put this in user.rb:
before_save :set_last_request
Warden::Manager.after_authentication do |user,auth,opts|
user.update_attribute(:currently_signed_in, true)
end
Warden::Manager.before_logout do |user,auth,opts|
user.update_attribute(:currently_signed_in, false)
end
def set_last_request
self.last_request_at = Time.now
end
def signed_in?
if self.currently_signed_in
if self.timedout?(self.last_request_at.localtime)
return false
else
return true
end
else
false
end
end
you can then use the signed_in? method to determine a users online status.

Related

Rails session doesn't expire at browser close [duplicate]

This question already has answers here:
Session not destroyed when closing browser - RailsTutorial.org
(2 answers)
Closed 5 years ago.
It's my understanding that the default behavior of Rails, when storing a session e.g. session[:id] = 1, is that it will save the id to a cookie, which will expire when the user closes the browser window. However, in my app, when I close (exit out) the browser and restart it, the browser still 'remembers' me as being logged in. Here is my controller code:
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:email])
if user && user.authenticate(params[:password])
log_in user
redirect_to user
else
flash.now[:notice] = "Invald email / password combination"
render 'new'
end
end
def destroy
end
end
and my helper file:
module SessionsHelper
def log_in(user)
session[:user_id] = user.id
end
def current_user
#current_user ||= User.find_by(id: session[:user_id])
end
def logged_in?
!current_user.nil?
end
def user_name
#current_user.first_name ? (#current_user.first_name + " ") : nil
end
end
I have nothing in the application controller nor did I ever mess with the initializers or config files regarding the session. What could be causing my session to persist and not expire the cookie?
Withing config/initializers/session_store.rb file add
Rails.application.config.session_store :cookie_store, key: '_app_session', expire_after: nil
Instead of
Rails.application.config.session_store :cookie_store, key: '_app_session'
Please have a look at here for more info.
The solution is to simply turn off cookies or reset cookies, which results in a new session being created with each visit. The solution looked like this:
On client side, write a beforeunload event handler.
From that handler make an AJAX request to logout URL, in your case it
might be a special view that deletes the cookie (see Rails docs on
cookies).
The AJAX request has to be synchronous to make sure beforeunload
event handler waits for its completion.
refer this https://codedecoder.wordpress.com/2013/12/04/clear-session-or-cookie-browser-close-ruby-jquery/ it might also help

sRails 5 Devise after_sign_in_path

I have a Rails 5 app with Devise. Each user has a role_id where they are assigned a role upon creation. I'm trying to use the after_sign_in_path_for method that Devise gives to redirect to a specific page on login based on the role.
Below is what I have so far, but it doesn't work when trying to sign out a disabled user.
class ApplicationController < ActionController::Base
def after_sign_in_path_for(resource)
case resource.role_id
when Role.find_by(name: "admin").id
root_path
when Role.find_by(name: "disabled").id
destroy_user_session_path
else
super
end
end
end
I'm able to sign in when I'm an admin user and it redirects. But if I try to sign in as a user whose role is disabled, it tries to tear down the session then raises an exception of No route matches [GET] "/users/sign_out". I know the method destroy_user_session_path expects a delete method but how can I pass this in the application controller?
What am I doing wrong here?
Update
I tried the sign_out(resource) as suggested in the first answer, and it raises an exception undefined methodto_model' for true:TrueClassin mymy_sessions_controller.rb` which I use to override the create method to set a login token and limit concurrent sessions. Here is the controller.
class MySessionsController < Devise::SessionsController
skip_before_action :check_concurrent_session
def create
super
set_login_token
end
private
def set_login_token
token = Devise.friendly_token
session[:token] = token
current_user.login_token = token
current_user.save(validate: false)
end
end
You can check roles inside MySessionsController#create and prevent logging if the role not valid instead of allowing user to login then logout
def create
unless current_user.role_id == Role.find_by(name: "disabled").id
super set_login_token
else
redirect_to new_user_session_path, alert: "You can't log in"
end
end
You can also use active_for_authentication? and inactive_message methods in user model to prevent him from login. in /app/models/user.rb:
def active_for_authentication?
super and self.role_id != Role.find_by(name: "disabled").id
end
def inactive_message
"You can't log in"
end
destroy_user_session_path is making [GET] "/users/sign_out" request.
You can use sign_out or reset_session function to delete session directly.
Hope this answer works for you.
Use devise's sign_out(resource) method instead of destroy_user_session_path. This method will destroy the user session.

Devise force logout for some users from Rails App

Is it possible to force logout for SOME of the users through Devise.
In my setup, I am using rails 4.1.15 with Devise 1.5.4. Sessions are persisted in db and there is no direct mapping with user ids. Is there a Devise way to logout against some of the users NOT ALL.
I tried resetting password as a proxy which logs out immediately but not always.
user_obj.update_attributes(:password => "some_random_string")
I can propose you next solution.
Add a new column for admin that called force_logout:boolean
In any your controller add a new action to set force_logout to true. Ex.:
# in admins_controller.rb
def force_logout
admin = Admin.find(params[:id])
admin.update_column(:force_logout, true)
redirect_to :back
end
In application_controller.rb add before_action to logout user if force_logout is true
before_action :check_force_logout
def check_force_logout
if current_user && current_user.force_logout?
current_user.update_column(:force_logout, false)
sign_out(current_user)
end
end
Too you need reset force_logout column after admin will be signed in. Usually you can do it session_controller.rb in action create.
Time to exhume this one. The only way I was able to solve this issue was by accessing the sessions. It is slow but it works.
def sign_out_different_user
#user = User.find(params[:id])
sessions = ActiveRecord::SessionStore::Session.where("updated_at > ?", Time.now - 480.minutes).all
sessions.each do |s|
if s.data['user_email'] == #user.email
s.delete and return
end
end
end
I needed to change the session expiration on the user object for my purposes, so I added this into the method, as well. YMMV
#user.session_expires_at = Time.now
#user.save

How to us a permanent cookie in a before filter to filter for first time guests

I'm setting up a simple survey on my web page.
I want to add a before_filter so that the same person can't take the survey more than once.
My idea is to
1) create and save a remember_token to each survey when it is submitted.
2) create a cookie based on that remember token to be placed on the submitter's browser
3) Every time some visits the page, use a before filter to make sure they don't have a cookie that matches a survey in the database.
I put together the below, but for some reason, it automatically redirects to the thanks_path, regardless of whether I have a remember token?
Why does it do this? Am I using the session cookie incorrectly?
My surveys_controller is as below
before_filter :new_visitor, only: [:new, :create]
def new
#this is the survey form
#survey = Survey.new
end
def create
#this submits the survey and creates a cookie on the client's browser
#survey = Survey.new(params[:survey])
if #survey.save
cookies.permanent[:remember_token] = #survey.remember_token
redirect_to thanks_path
else
render action: "new"
end
end
def thanks
#blank page that just says, "thanks for taking the survey!"
end
def new_visitor
# if a browser has a survey cookie, redirect to thanks page
unless Survey.find_by_remember_token(cookies[:remember_token]).nil?
redirect_to thanks_path
end
end
I am creating the remember token in my Survey model.
class Survey < ActiveRecord::Base
before_save :create_remember_token
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64
end
end
I think you need to test for the existence of the cookie[:remember_token] before using it as an argument to find_by_remember_token(). Only if cookies[:remember_token] is not nil and a record is found do you redirect to the thanks_page.
if cookies[:remember_token] && Survey.find_by_remember_token(cookies[:remember_token])
redirect_to thanks_page
end
unless Survey.find_by_remember_token(cookies[:remember_token]).nil?
this means if Survey not nil then redirect, i think you need to change to
unless Survey.find_by_remember_token(cookies[:remember_token])
or
if Survey.find_by_remember_token(cookies[:remember_token]).nil?

How force that session is loaded?

I'm working on an application that needs to use session id information. My session is stored in cookies. The problem I have is that my session is not immediately available to the controller when a user comes to the site for the first time. I think I may be missing something about how sessions are initialized in Rails. But I'm positve about the fact that the session is not loaded because this is the output of session.inspect:
#<Rack::Session::Abstract::SessionHash:0x15cb970 not yet loaded>
Here is how to reproduce the problem with Rails 3.2.11 and ruby 1.9.3:
Create a new application with a test controller:
rails new my_app
cd my_app/
rails g controller test
rm app/assets/javascripts/test.js.coffee
touch app/views/test/index.html.erb
Try to get the session id in that controller:
class TestController < ApplicationController
def index
puts session[:session_id]
puts session.inspect
end
end
Add the needed routes:
MyApp::Application.routes.draw do
resources :test
end
Then access the application and see what it does:
rails server
got to: http://localhost:3000/test
That is the output in the console:
#<Rack::Session::Abstract::SessionHash:0x3fd10f50eea0 not yet loaded>
Then again http://localhost:3000/test and this time we have a session:
400706c0b3d95a5a1e56521e455075ac
{"session_id"=>"400706c0b3d95a5a1e56521e455075ac", "_csrf_token"=>"Euaign8Ptpj/o/8/ucBFMgxGtiH7goKxkxeGctumyGQ="}
I found a way to force initialization of the session. Accessing the session apparently does not force initialization but writing into the session does. What I do in my controller is this now:
class MyController < ApplicationController
protect_from_forgery
def index
session["init"] = true
do_stuff
end
end
Still I'm not sure if this should be considered normal behavior in Rails. It doesn't look right to me having to write into the session to force initialization. Reading should be enough.
I agree with #joscas answer but instead of writing a value, I'd delete it as to not have redundant data.
class MyController < ApplicationController
protect_from_forgery
def index
session.delete 'init'
do_stuff
end
end
The session is loaded this way too.
Note: Make sure you don't use the key to be deleted in your application.
Here's some relevant code from ActionDispatch::Session:
def [](key)
load_for_read!
#delegate[key.to_s]
end
private
def load_for_read!
load! if !loaded? && exists?
end
Which implies that the session object will be loaded as soon as you access any value by its key via [].
I don't really understand your question. If you require a user to register or sign in before being able to access the site there should be no problem. When creating a user his information is immediately stored in a cookie. For example:
User controller: (registering is done through users#new)
def create
#user = User.new(params[:user])
if #user.save
cookies.permanent[:remember_token] = user.remember_token
redirect_to root_path, notice: "Thank you for registering!"
else
render :new
end
end
Sessions controller: (signing in is done through sessions#new)
def create
user = User.find_by_email(params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
cookies.permanent[:remember_token] = user.remember_token
redirect_to root_path, notice: "Logged in."
else
flash.now.alert = "Email or password is incorrect."
render :new
end
end
So the problem probably boils down to the fact that the cookie that stores session_id wasn't created yet at the point where you tried to access the session.
When you read session object, Rails calls a private method session.load_for_read!, but, as its name suggests, it only loads session for reading and doesn't instantiates the session if it simply doesn't exist.
On the other hand, calling session.merge!({}) (for example) forces session instantiation.
Yo! #zetetic's answer above made me realize that we can just do
session.send(:load!)
and it'll load the session 🙂.

Resources