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 .
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.
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:
Configuration
Here are the steps I took in order to get auto-session-timeout to work with Devise:
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
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
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.
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.
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.
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.
Related
One of the form pages on our website is showing, occasionally, inconsistently, a fair number of ActionController::InvalidAuthenticityToken errors.
Viewing the logs we can see this occurs when:
a) user visits form (works)
b) clicks an option (works)
c) clicks another option DAYS later (causes CSRF error)
We believe the issue is that after (b) the user quits their browser, which erases session cookies, then when they re-open their browser if they have "reopen pages" enabled on their browser it re-displays the page, but there is no session and therefore the expected CSRF token is missing from their session.
We've added some code to handle that particular CSRF error with a custom error message.
We'd like to TEST the new code in rspec by simulating the same sequence of events.
That requires a way to erase Capybara's session for that user, simulating what happens if browser is closed then reopens:
visit form_url
click_button button_a
?? erase session cookie, with page object still 'open', so form object still exists??
click_button button_b
Using reset_session! wipes the page object too.
In an Rspec spec, how can we erase/invalidate the "browser" session cookie so when we click another button on the form, a Rails CSRF error will be triggered?
I think there is a simple way to do this that involves making a request to your Rails backend. The high level is that you expose a route and a controller action to test mode that you do not expose in any other environment.
Make the reqeust to this endpoint in the specific situation you want to clear the session cookie.
In the controller action, you clear the session cookie and and set this so that the request you have just made is not going to set a new session
# config/routes.rb
get 'clear-session', :to => 'application#__clear_session' if Rails.env.test?
and
# controllers/application.rb
if Rails.env.test?
def __clear_session
request.session_options[:skip] = true
cookies['__yourapp_session'] = { :value => '', :expires => Time.at(0) }
end
end
I'm using Devise, but not using the Devise controllers directly because I'm performing all of the actions through a custom built GraphQL API. One issue I have, for example, is that after enabling confirmable, if a user tries to sign in and I call Devise::Controllers::Helpers#sign_in the user gets redirected to /api/v1/users/sign_in, which doesn't exist and it would be wrong even if it exist. Instead, I need the failure to sign in to be returned back to my code (return value, exception, whatever), so that my API can encode that response to the frontend.
How can I do that?
For example, this is my log in function:
def resolve(email:, password:)
user = User.find_for_authentication(email: email)
if user&.valid_password?(password)
context[:sign_in].call(user)
{ current_user: user }
else
{ errors: [{ message: 'Email or password incorrect.' }] }
end
end
context[:sign_in] is set up in the GraphqlController by including Devise::Controllers::Helpers and then simply:
context = {
current_user: current_user,
sign_in: method(:sign_in),
sign_out: method(:sign_out)
}
Note: I am not using GraphqlDevise because I don't want to use Devise Token Auth, I'm using cookies.
I believe passing devise's sign_in/sign_out methods via context is probably a deadend.
The suggestion in the comment to your question from #Int'l Man Of Coding Mystery is good ie you could use: https://github.com/graphql-devise/graphql_devise.
If you're not keen in introducing another dependency and figuring out how to wire everything you can perhaps go with overriding devise's SessionController.
See for some examples here: Rails - How to override devise SessionsController to perform specific tasks when user signs in?
(but also don't hesitate to look at the source code for the matching Devise release: https://github.com/heartcombo/devise/blob/master/app/controllers/devise/sessions_controller.rb)
Depending on your use case you might be even able to do what you need by using some of the config options - e.g. you can perhaps try to override after_sign_in_path etc.
I am developing a system which contains many roles and one of the roles is ADMIN which can access critical part of the system, I want to limit the user's session which when he/she don't interact with the system for certain period of time, its session gets expires and the next time he/she should log in again, How can I do this?
Devise has a timeoutable module that you can use.
In your User model you would include :timeoutable with your devise models and in the devise.rb initializer you would configure it comparable to:
# ==> 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 = ENV['YOUR_TIME'].to_i.minutes
If you want to be more flexible with your user types you can add a method something like this for your User model:
def timeout_in
if self.type == 'Admin'
45.minutes
else
60.minutes
end
end
That would be used instead of setting timeout_in in your initializer.
Edit: My first response was similar to the first answer in the thread below, but the second answer in that thread might be a better fit. Since it works directly with the session_store.
Here's a useful StackOverflow link that can provide extra info: Rails 4: Session Expiry?
This documentation has some info on the methods available for session manipulation: http://api.rubyonrails.org/classes/ActionDispatch/Session/CookieStore.html
I have an app that is using Devise, I would like that after a user signs up, they are directed to a specific page, this page calls an API and saves the value from the API, I need this page to only be accessible or available after a user completes the sign-up form and clicks submit, and is then redirected to this page.
I do not want this page or URL accessible any other way but after sign-up, as the API will send a new value if accessed again. How can I accomplish this?
Once a user signs up they will be redirected to the page calling the API:
def after_sign_up_path_for(resource)
api_call_path ##path that can only be accessed after sign_up
end
The API is called and the response from the JSON data is automatically saved to the database once the page is opened, if the page gets opened again a new JSON response will be received with new data, which is what I would like to avoid.
So in a nutshell, my question is how can I restrict access to a specific path, and only make that path accessible if a user completes the sign-up form (devise) OR is there a way that I can run the code from the controller using a callback/filter after the user is created through the User model?
I was just busy with something similar. You do not need to direct the user to a page to run the code, you can just run the code that needs to be run after the user logs in the first time.
You need to create a Session Controller, and create a conditional statement that checks if the user has logged in previously:
#config/routes.rb
devise_for :users, controllers: { sessions: "sessions" }
#app/controllers/sessions_controller.rb
class SessionsController < Devise::SessionsController
def after_sign_in_path_for(resource)
if resource.sign_in_count == 1
##Do something awesome
else
root_path
end
end
end
As Emmanuel advised you can check futher info on the Devise Controllers.
Let's call the moment between after sign_up and showing the specific page - state A. So in the specific page controller you need to know - is the user in state A. You can achieve it by
1) saving to db (server side) that user is in state A after sign up and resetting state after showing specific page (you can do resetting triggered by client side to guarantee that page is showed).
2) saving to cookies (client side) after sign up then do as above.
Second solution is more clean in my opinion, but I do not know how strict is the rule to show only once
You can customize devise users controller by issuing
rails generate devise:controllers [scope]
Then customise UsersController such that after user is saved you can call your api code there
eg
def create
#user = ....
if #user.save
#user.call_api_method()
else
......
end
end
For more information check Configuring controllers
I am trying to manage the login session of users that navigate in my RoR3 application.
I set a cookie when they sign in and I would like that their authentication expires after a few minutes of inactivity, for example 15 minutes.
How to achieve that?
This doesn't directly answer your question, but I strongly suggest using something like Devise for authentication instead of rolling your own.
Devise provides a timeoutable configuration flag, as well as a timeout value covering how long user sessions can be inactive before being logged out.
You could setup a property in your session. Something like
session[:expire_time] = 15.minutes.since
Then, in your applicationController you can check if your user has been away enough time to be logged out, if not then you renew his expiration time, something like:
class ApplicationController < ActionController::Base
before_filter :check_expire
def check_expire
if session[:expire_time] and session[:expire_time] < Time.now
#your code to logout the user
else
session[:expire_time] = 15.minutes.since
end
return true
end
end
You can also set the expiration time of the cookie that sets their session. In your configuration :
env["rack.session.options"][:expire_after] = 15.minutes
This works perfectly for the use case you described, because it will be reset every time the server responds, but it gives you a little less control.
Here's a good article about it :
http://augustl.com/blog/2010/dynamic_session_expiration_time_in_rails_3