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 🙂.
Related
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
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 .
I'm building a sessions controller in my rails app, and I'm just not sure why something is working here. In the create and destroy actions, session[index] is assigned to either nil or a user id. But this sessions hash isn't defined anywhere (as far as I can see). Why is this working? Can anyone clarify this for me?
(for the sake of clarity, there is no sessions model)
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by_email(params[:email])
if user && user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to products_url, :note => "Logged in!"
else
render "new"
end
def destroy
session[:user_id] = nil
redirect_to products_url, :notice => "Logged out!"
end
end
The session instance method functions like a Hash and is part of the Rails API.
Rails does all the work of setting up an encrypted, tamperproof session datastore. By default, session data is saved as a cookie in the browser. You can specify other storage mechanisms but CookieStore is the default and the most convenient.
The CookieStore default is set in the config/initializers/session_store.rb file:
Rails.application.config.session_store :cookie_store, key: '_learn-rails_session'
You can learn more about sessions in Rails:
RailsGuides Action Controller Overview
Rails API ActionDispatch::Session::CookieStore
For more information, I've written a Rails Devise Tutorial that shows how sessions are managed with the Devise authentication gem.
By default sessions are stored in a cookie in the client-side (i.e. the user's browser's cookie). It is not stored on the server-side (i.e. where the Rails app is actually running.)
When you use the session hash, Rails is smart enough to look/ask for the session information accordingly. In the default case, Rails knows to set the session information in the browser's cookie, or retrieve the information from the browser's cookie.
You can also pick where to put your session store by setting the config.session_store configuration variable.
See the Rails guide for more info.
I'm developing a rails app that creates a widget that you can put on your website. When the widget is displayed (on a website with a different host) to a user who is logged in my app, I would like to display him some additional admin options.
What would be the best and easiest way to figure out if the user is logged in the app?
I was thinking of storing the IP when user logs in, and then compare the IP from the request that is sent to the widget controller.
I followed the Omniauth Railscast Episode and have been using session variables and a SessionsController to create and destroy sessions when the user logs in and out.
class SessionsController < ApplicationController
def create
# create user if new user, or find user account if returning user.
session[:user_id] = user.id
redirect_to root_url # or wherever
end
def destroy
session[:user_id] = nil
redirect_to root_url # or wherever
end
end
Then in the Application Controller,
class ApplicationController < ActionController::Base
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
end
You can then easily determine if a user is logged in or not via if current_user, or equivalently, if session[:user_id] is nil.
IP could be deceptive. Try cookies.
Edit: not only in an actively deceptive manner (i.e. spoofing/Tor) but rather if two people are on separate sites from the same public IP, then you have a false correlation.
If you watch over any of Ryan Bates Authentication related Railscasts you'll see a recurring theme when creating sigin/signout functionality and I wanted to understand that a little bit more clearly.
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
For example usually in a session controller the create action will contain an assignment to the sessions hash such as session[:user_id] = user.id given that the variable user is set to an Active Record Object.
The above helper method is then used throughout the views to find the current signed in user.
However when signing out the destroy action contains only the line session[:user_id] = nil
My question is wouldn't #current_user also be needed to set to nil since it would be set to the previous User that was signed in?
Typically after setting session[:user_id] = nil your controller will return so #current_user still being active doesn't matter. You have to remember that #current_user only exists for that request, the next request that comes through is a new instance of that controller class.
You are right that if you did something like this:
def destroy
session[:user_id] = nil
logger.debug current_user.inspect # Current user is still set for this request
redirect_to admin_url, notice => "You've successfully logged out."
end
You would see the user information in the log file, but normally you are doing a redirect right after clearing the session[:user_id] so that controller instance is done.