Rails session doesn't expire at browser close [duplicate] - ruby-on-rails

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

Related

Rails Tutorial (M.Hartl) 3rd Edition, Chapter 8, Why does the app fail to forget login status?

After logging into the application and then closing the browser completely, I re-open the browser expecting to be logged-out. However I am still logged in?
I am in section 8.2.3 Changing the layout links and apart from the above, the application looks and runs as expected. Also the code is as it is from the tutorial. I think these are the relevant files:
app/helpers/sessions_helper.rb
module SessionsHelper
# Logs in the given user.
def log_in(user)
session[:user_id] = user.id
end
# Returns the current logged-in user (if any).
def current_user
#current_user ||= User.find_by(id: session[:user_id])
end
# Returns true if the user is logged in, false otherwise.
def logged_in?
!current_user.nil?
end
end
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
redirect_to user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
end
end
Ordinarily, the session is cleared when you close your browser, so you should be logged out automatically in this case. Some browsers remember your session anyway, though, as discussed in this footnote, and that could be what's going on here.
After logging into the application and then closing the browser completely, I re-open the browser expecting to be logged-out. However I am still logged in?
Because you are still logged in your application. Your browser remember login data by a cookie used to trace the session.
To log out you can use the logout link explained in the tutorial or you can clean your browser cache.
because you have a cookies, that the way you have like in every page sessions, that's the way are created. They are stored. If you want to deleted, just go to config, delete cookies
According to Mhartl's answer, you will experience the user not being logged out once you close the browser if you are using Chrome (I don't know why exactly) but when you open your application via firefox, user will be logged out automatically (again not sure how) as stated in Chapter 8 .

Rails Session not persisting after browser refresh

Navigating within the app and re-directs are all fine, its just when the browser refreshes the user has to log back in. I only want the session to expire when the browser closes and not on refresh..
My session_store.rb
Rails.application.config.session_store :cookie_store, key:
'_workspace_session'
My sessions controller new and create actions:
def new
end
def create
merchant = Merchant.find_by(email: params[:email])
if merchant && merchant.authenticate(params[:password])
session[:merchant_id] = merchant.id
log_in(merchant)
flash[:success] = "You were logged in successfully"
redirect_to merchant_path(merchant.id)
else
flash.now[:danger] = "Snap! either your email or password is
incorrect. Try again"
render 'new'
end
end
It's probably because you don't have a sessions helper that adds a cookie of the logged in merchant's credentials and checks them every time you reload the page:
First, add your sessions_helper.rb to your main controller:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
include SessionsHelper
end
Here is your sessions helper:
module SessionsHelper
# Returns the user corresponding to the remember token cookie.
def current_merchant
if (merchant_id = session[:merchant_id])
#current_merchant ||= Merchant.find_by(id: merchant_id)
elsif (merchant_id = cookies.signed[:merchant_id])
merchant= Merchant.find_by(id: merchant_id)
if merchant && merchant.authenticated?(cookies[:remember_token])
log_in merchant
#current_user = merchant
end
end
end
#logs the merchant in.I don't know what your log_in method does there in the question, if it does the same, you are doing it two times.
def log_in(merchant)
session[:merchant_id] = merchant.id
end
end
Adapted from RailsTutorial <- I'd recommend you read this. A lot of great stuff in there.

Rails + iOS authentication without devise

I have a very simple rails app that I rolled my own authentication for, based on the rails tutorial book by Michael Hartl (http://www.railstutorial.org/book/modeling_users). The app is a content management system for an equally simple iOS app. I am aware that devise is very popular, but I really do not think it is necessary for this project. I want to be able to link my iOS app to my rails app, but everywhere I look the only advice I can find is how to do it with devise. All I want to do is have the user be presented with a login screen so they can establish a session and then I can handle all of the permission logic on the rails end of things. Here are a few things to give you an idea of my current authentication scheme:
My session controller:
class SessionsController < ApplicationController
def new
end
##
#Use the email in the nested hash to find the right user
#Check to make sure that the user authenticates with the given password
##
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
sign_in user
redirect_back_or root_url
else
flash.now[:error] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
current_user.update_attribute(:remember_token,User.digest(User.new_remember_token))
cookies.delete(:remember_token)
self.current_user = nil
session.delete(:return_to) #not sure if this should really be here or if better way to fix bug
redirect_and_alert(root_url, "User Successfully Logged Out!",:success)
end
end
Sessions helper:
module SessionsHelper
##
#set the remember token for the user
#make the cookie reflect that token
#update the users remember token column
#set the user being passed in as the current user
##
def sign_in(user)
remember_token = User.new_remember_token
cookies.permanent[:remember_token] = remember_token
user.update_attribute(:remember_token, User.digest(remember_token))
self.current_user = user
end
#set the current user
def current_user=(user)
#current_user = user
end
#Helper current user method
def current_user
remember_token = User.digest(cookies[:remember_token])
#current_user ||= User.find_by(remember_token: remember_token)
end
#Is the requesting user the current user
def current_user?(user)
user == current_user
end
#Is the user signed in?
def signed_in?
!current_user.nil?
end
#Store user request info for friendly forwarding
def redirect_back_or(default)
redirect_to(session[:return_to] || default)
session.delete(:return_to)
end
#Store user request info for friendly forwarding
def store_location
session[:return_to] = request.url if request.get?
end
#Authorization
def signed_in_user
store_location
redirect_to signin_url, notice: "Please sign in." unless signed_in?
end
def super_user
redirect_and_alert(root_url,
"You are not allowed to do that. Contact the admin for this account.",
:error) unless (current_user.role.id == 1)
end
def super_user_or_admin
redirect_and_alert(root_url,
"You are not allowed to do that. Contact the admin for this account.",
:error) unless (current_user.role.id == 1 || current_user.role.id == 2)
end
def is_super_user
current_user.role.id == 1
end
def is_admin
current_user.role.id == 2
end
def is_regular_user
current_user.role.id == 3
end
end
You could go for a token-based system rather than a session-based system.
Create an attribute called authentication token for every user. This token can be generated and assigned during sign-up. The token itself can be generated using simple techniques such as SecureRandom.hex(n), where n is the length of the random hex number generated.
After sign-in/sign-up from the app, send the authentication token in the response from the server. You can then have the iOS app send the token along with every subsequent request to the server.
Have the server check the token every time a controller is hit with a request. This can be achieved using the before_filter. So a sample controller could look like this:
before_filter :authenticate_user
def authenticate_user
# assuming the parameter sent from the app is called auth_token
auth_token = params[:auth_token]
user = User.find_by_authentication_token(auth_token)
if user.nil?
# what to do if user does not exist
else
# what to do if user exists
end
end
Typically for mobile applications tokens are used instead of sessions. The token is essentially just a string that your Rails app will give each mobile device to verify it has access.
Step 1 is to have your mobile app authenticate against the Rails app (this can be through a custom email/password login or through a 3rd party like Facebook).
Have your Rails app send a token to the mobile application
Store this token in UserDefaults on iOS (help link: http://www.learnswiftonline.com/objective-c-to-swift-guides/nsuserdefaults-swift/)
Whenever your app makes a request (or websocket connection, etc.) to the Rails app, send the token (usually as a header).
Whenever the iOS app opens, check for the token in UserDefaults. If it is there, then you skip having the user authenticate again.
If you want to get really fancy, the Rails app should only accept a token once and issue new-tokens on each request. So your iOS app would send a token on each request and then grab a new one with each response. This is a more secure practice so someone does not grab a token and mess with your Rails app.
This diagram covers the high-level process (it uses FB login for initial authentication, but you can plug yours/an alternate system in instead): http://www.eggie5.com/57-ios-rails-oauth-flow.

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 🙂.

"Rails by Example" authentication implementation

In Michael Hart's book this code is used to implement authentication:
module SessionsHelper
def sign_in(user)
cookies.permanent.signed[:remember_token] = [user.id, user.salt] #permanent
# -alternatively-
# cookies.signed[:remember_token]={
# :value => [user.id, user.salt],
# expires => some_time.from_now
# }
current_user = user
end
def current_user=(user)
#current_user = user
end
def current_user
return #current_user ||= user_from_remember_token
end
private
def user_from_remember_token
#passes array of length two as a parameter -- first slot contains ID,
#second contains SALT for encryption
User.authenticate_with_salt(*remember_token)
end
def remember_token
#ensures return of a double array in the event that
#cookies.signed[:remember_token] is nil.
cookies.signed[:remember_token] || [nil,nil]
end
end
It does it's job very well, I can either log in for an infinite amount of time, or for a limited period of time as I wish.
But it has a downside, cookies are stored on the client and they dont go away even if the browser is closed
Now I was wondering, since rails sessions get destroyed after a browser is closed, how would I combine them and the cookies presented here to implement authentication with the following characteristics:
-- if a user logs in,
the data should be stored in a session
so that after a user closes his browser they get logged of
-- if a user logs in, with a 'remember me' checkbox selected
their data should be stored in a cookie with a long expiration date
What would be a take on this that remains secure and simple?
I googled on the web and found nothing recent enough (Rails 3) that
could guide me in the right direction.
I was thinking of creating 2 separate modules for sessions and cookies and fire their respective sign_in methods in the controller whether a remember_me param was present or not, but that would seem a lot of duplication.
PS I am not looking into any authentication gems to provide this functionality, id prefer to implement it on my own.
Thanks

Resources