Rails 3 Cookie Based Sessions Question - ruby-on-rails

With Rails 3, the default session storage mechanism is cookie_store. I assume that this means that the contents within the session hash are serialized, encoded and stored within a cookie in the browser? Does this mean that nothing (or very little) of the session is stored in the server?
I've had a few issues where I had a cookie overflow error and I'm assuming because I kept on adding to my user instance (which was also linked/fetched from the cookie).
u = session[:user]
u.add_this lots_of_data
so eventually I got a cookie overflow error.
Am I correct about this? Are sessions fully stored within cookies in Rails 3 (by default)?

Yes, if you use the cookie store, the session data is stored in the cookie. If you'd like to store it on the server, you will need to use another session store.
However, if you are storing model objects or "lots of data" in the session, you are most likely doing it wrong in the first place. Your data should go to the database, and the session should only contain as much information as you need to retrieve it.
In you case, this would mean to store the user id int he session, and load the user from the db in a before_filter.

Yes, you are right. The problem might come up if you keep on adding data to session.
But there are some other things that affect it.
Once, I ended up with CookieOverflow error, and the reason was the flash[:notice] messages.
If you use flash[:notice] = "message" and then redirect, the text "message" will be stored in the cookie. If the size of the text u pass is more than 4KBs, you get "CookieOverflow" error

Related

How OpenStruct stored in session

I have some controller. In this controller I get OpenStruct object and want to save it to app session. Next code works fine:
session[:info] = OpenStruct.new(first_field: 1, second_field: 'two')
p session[:info] right after this line prints
#<OpenStruct first_field=1, second_field="two">
But after this I do redirect to another controller, and when I write p session[:info] in this controller I get
{"table"=>{"first_field"=>1, "second_field"=>"two"}}
So, why do I get this, and how can I load correct OpenStruct instance?
A session usually consists of a hash of values and a session id,
usually a 32-character string, to identify the hash. Every cookie sent
to the client's browser includes the session id. And the other way
round: the browser will send it to the server on every request from
the client.
You should either serialize your objects before storing them in the session.
session[:info] = OpenStruct.new(first_field: 1, second_field: 'two').to_yaml
and retrieve it using
YAML.load(session[:info])
from the rails documentation
Do not store large objects in a session. Instead you should store them
in the database and save their id in the session. This will eliminate
synchronization headaches and it won't fill up your session storage
space (depending on what session storage you chose, see below). This
will also be a good idea, if you modify the structure of an object and
old versions of it are still in some user's cookies. With server-side
session storages you can clear out the sessions, but with client-side
storages, this is hard to mitigate.
or change your session store from cookie_store to cache_store
In your environment change
config.session_store :cookie_store
to
config.session_store :cache_store

Adding User-Agent & IP to a stored Session -RecordNotSaved -Rails

I'm currently building an in-house analytics system which tracks where a visitor clicks throughout each session, whether they are logged into a User account or are a visitor without an account.
I am currently saving my sessions to the database thanks to changes made through sessions_store.rb, however in addition to the session_id, I am trying to figure out how to add both UserAgent details and a visitor's IP to the sessions table.
I've tried a couple solutions but all have failed - my current solution appears to be the closest, however I keep encountering an ActiveRecord::RecordNotSaved error after updating the Session's attributes.
I am currently using a before filter in application controller:
before_filter :set_useragent_and_ip_in_session
def set_useragent_and_ip_in_session
if session
sess = request.session_options[:id]
#session = Session.where(session_id: sess).first
#session.update_attributes(:ip=>request.remote_ip, :user_agent=>request.user_agent)
#session.save!
else
end
end
I've inserted a debugging statement in my views and have played around the code in pry - the #session is valid and displays the #session.user_agent properly .... however when saving to the DB it justs rollsback.
When I use save!, I receive a Completed 422 Unprocessable Entity in the logs in addition to the following (pasted to gist to conserve space):
https://gist.github.com/anonymous/8019c2426334f395a5fd
Thanks! Any help would be very appreciated.
I would recommend rethinking your schema. A session can last many requests and you want a record per request. Also notice how your moving data off the request object and trying to store them into a session table. Instead make your very own request table and make a before filter that moves all the data you want from the request object to the request table. Now a session will have many requests.
I'm not sure why your record isn't saving however I would wager naming a model Session is conflicting with rails. By that same token when you make your request table you should give it a unique name like maybe SessionMeta and RequestMeta. Then SessionMeta could have many RequestMeta.

Passing a token securely from one controller method to another

I want to generate a random token for my user. For UX reasons, I want to generate this token in MyController#new in order to show it in new view. Then I need to pass it to create method. However, I want to prevent user from changing it.
I know of 3 ways to do it:
pass it as hidden field
pass it through sessions
write it to database, marked as incomplete, then set it to complete in create method
The first two approaches are not secure, while the last is overkill.
Is there a way to securely pass a parameter from new to create method in controller?
Sessions in Rails and # and above are very very secure because they are hashed. The official library says there are no practically evidences of session hash been compromised so the user can only delete that token from the browser but they can't change it to a logically correct value.
What you can do is save the value in sessions and make a hash value yourself from that token and save it also in session then on the receiving side you can regenerate the hash from the session and verify the value.
no user can edit both the session values that they match. If its not matching you can discard the values and throw to an error page.
I hope i answered you, if there is any misunderstanding pls ask.

messaging from the model level to controller level for ajax purposes

I have a page called /examples/1/duplicate
on there is an ajax form which kicks off /examples/1/run_duplicate
the controller method for run_duplicate runs Example.duplicate(session)
I pass in the session, which I understand is a holy no-no.
the duplicate model code is roughly:
def duplicate(session)
session[:duplicate] = 0
duplicate_foobars
session[:duplicate] = 1
duplicate_snafus
session[:duplicate] = 2
duplicate_widgets
session[:duplicate] = 3
end
I have another controller method on the duplicate page which is doing a long poll: /examples/1/check_dupe_status
The purpose was to get the updated status from session[:duplicate], tweak a progress report,
and report it back to the user via ajax so they could see the progress of Example.duplicate() .
sessions did not update the way I hoped. I can see the code run, but the sessions don't update, so /examples/1/check_dupe_status never knows that anything has kicked off.
Given that I did this wrong from the start, what is the correct way to notify a user about the state of Example.duplicate() ?
Sounds like you've got two separate requests, with one writing to the session and one trying to read from the session concurrently. Is that correct? Stop reading if it's not.
This can not work because the "session" is just a cookie - a header on the HTTP response that's downloaded to your browser as a cookie, then re-uploaded on the next request, then re-downloaded on the next response, ad nosium. In your code, here is the order of operations:
/examples/1/run_duplicate writes "0" to what is essentially a Ruby Hash, representing your session cookie
/examples/1/check_dupe_status reads the values from the session cookie that was just sent from your browser with this request. It probably didn't have anything at all in :duplicate, so it will appear as blank.
/examples/1/run_duplicate writes "1" to the Ruby session Hash
/examples/1/check_dupe_status reads, again, the session cookie which that request sent - nothing has changed
/examples/1/run_duplicate writes "2" to the Ruby session Hash
/examples/1/check_dupe_status reads from the session cookie it originally sent - no change
/examples/1/run_duplicate writes "3" to the Ruby session Hash and the request finishes, sending the session back as a cookie with a value of 3 at :duplicate.
/examples/1/check_dupe_status is still sitting there like a dufus, reading the blank session cookie it originally sent
At some point, /examples/1/check_dupe_status may timeout, and it may return with the session cookie. But guess what? Since :duplicate was never set in that session cookie, it will overwrite the one in your browser, and :duplicate will be blank in the next request you send.
Hopefully I expressed that clearly. Basically you're hitting a race condition in your cookies, and that's very difficult to overcome if you're sending concurrent requests from the same browser.
The best way to handle this would be to write your duplicate 0,1,2, etc. values to some database table. Your long-polling request could then just read out of the database. A little less efficient perhaps, but of course it has the advantage that it could work.

Can't understand sessions in Rails

Please don't bit my for my misunderstanding.
The sessions are very new for me, and i have some problems.
Okay i read many information about sessions and especially rails session. But this don't give me right imagine about sessions.
Did i understand right, when users send request to server (get) -> Server create a new session (and store this some file in hard drive with session id), session id -> is a random generated num? so, server create a new session (and store session on drive) after this server send back answer to client and set session_id in cookies?
Ok, i debug some params and see some results:
debug(session):
{:_csrf_token=>"jeONIfNxFmnpDn/xt6I0icNK1m3EB3CzT9KMntNk7KU=", :session_id=>"06c5628155efaa6446582c491499af6d", "flash"=>{}}
debug(cookies):
{"remember_user_token"=>"1::3GFRFyXb83lffzwPDPQd", "_blog_session"=>"BAh7CDoQX2NzcmZfdG9rZW4iMWplT05JZk54Rm1ucERuL3h0NkkwaWNOSzFtM0VCM0N6VDlLTW50Tms3S1U9Og9zZXNzaW9uX2lkIiUwNmM1NjI4MTU1ZWZhYTY0NDY1ODJjNDkxNDk5YWY2ZCIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFzaHsABjoKQHVzZWR7AA==--348c88b594e98f4bf6389d94383134fbe9b03095"}
Okay, i know, what _csrf_token helps to prevent csrf.
session_id -> is id of the session which stored on hard drive (by default)
but what is _blog_session in cookies?
also, remeber_user_token containes my id (1::*) and what about second part, what is it?
Sorry for this stupid questions, i know what i can easy use any nice auth-plugins (authlogic/clearance/devise), but i want to fully understand sessions.
Thank you.
(also sorry for my english, this is not my native language)
remember_user_token is probably set by your authentication plugin, it is encrypted string, which is stored in users table and is used to authenticate him. Details can vary between plugins.
Second part: you are probably using cookie based session store (it is default),
So, _blog_session stores your encrypted session data.
More about cookie based sessions here and here.
The name "_blog_session" is set in config/initializers/session_store.rb
It looks like:
# Your secret key for verifying cookie session data integrity.
# If you change this key, all old sessions will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
ActionController::Base.session = {
:key => '_blogs_session',
:secret => '07fb6f0d41af4ae06aebb1696fcbb5a5398d4a08570744a4cd53ff237020c43a2022b4041d617d95bcf3f5c4601c7e6c1646eecfc157cc200e7dfedd7d7c6813'
}

Resources