When is a session destroyed in rails? - ruby-on-rails

To my understanding,
when a session is initialized for some user,
the session gets a hash and session_id which identifies it.
ex. session[key]=value
session_id = 23f8fzsj2048j20j
Now, when logging out a user, I know you can simply set
session[:user_id] = nil
But what happens to the actual session hash?
If I'm not wrong, if there is User A, B, and C, they each get assigned a unique session, for example with User A with session_id = 12345abc, User B with session_id = 23456abc, and so on.
Does this not create infinite amount of session hashes then?
Are they garbage collected when they do not get used?

This depends entirely on the mechanism you're using to store hashes, but the short answer is: No, sessions are not garbage-collected, and they generally don't need to be.
Sessions are (by default) stored using CookieStore. There is no server-side data, all of the cookie data is encrypted and stored in a cookie. The user's browser is responsible for clean-up.
You can use alternative session storage engines, such as ActiveRecord sesssion store which stores session data in the database. Doing this requires you to manually choose when to consider a record "expired", and to implement your own clean-up code, again, they are not automatically "garbage collected": How does Rails know when to delete a record from the `sessions` table?

I am not sure what you mean by session_id, but by default sessions are using a cookie that is saved in your users' browser.
If you'd like more reference for the code involved I suggest you read this, https://github.com/rails/rails/blob/3ac3760c69e6e6914c5ddae138856b3c82ac0f20/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb, and then go on and see the Rack implementation for cookies.
As for nullifying a session in the context of logging a user out, most authentication frameworks check for a special cookie with a user id that is set once a user has logged in and when that value is not found in the cookie your are essentially logged out.
The hash itself which gets initialized in the request cycle lives in memory and will be garbage collected at the end of the request-response cycle, in a very naive way, your hash lives in the heap and gets garbage collected.
The data that it is referencing to lives in your browser's cookiejar and has it's own mechanisms for cleaning it.

Related

Is session a method or hash in Rails? Slightly confused

Got a really simple question. I'm doing the railstutorial by Michael Hartl and it talks about using the session method:
Logging a user in is simple with the help of the session method defined by Rails... We can treat session as if it were a hash, and assign to it as follows:
session[:user_id] = user.id
It says you can treat session as if it were a hash, but I'm confused because it is called the session method, so is anything actually being called? My guess is that by inserting into the session hash, there is a session function that looks into the hash to see if there is anything present? I'm not really sure how it works.
Would be rude not to mention the Session documentation:
All session stores use a cookie to store a unique ID for each session (you must use a cookie, Rails will not allow you to pass the session ID in the URL as this is less secure).
Basically, each time someone visits your Rails app, it will create a small cookie in their browser, identifiable by a unique ID (not user ID).
This cookie is essentially a Ruby hash, hence why you can store hashed data inside it:
session[:your_hash] = "TEST"
This will allow you to store small snippets of data (such as user_id or others) through requests.
The main reason Rails has this is down to HTTP being a stateless protocol.
Stateless protocols are contrary to stateful protocols; they don't retain the state between requests, thus you have to reinvoke data, etc, each time a new instance of the application is accessed.
Simply, this translates into Rails being a "dumb" system - only remembering data you send it each request. Session variables have been used by developers for decades to provide base information about users / preferences etc, allowing you to "rebuild" a user with each request.
This is why you have to save the user_id as a session - each time you wish to reference a logged-in user's data, it has to be built from that id stored in the sessions hash.
It is a method that returns an object which supports (some of) the same methods a Hash object supports, such as [] and []=. Actually, it is an ActionDispatch::Request::Session object.

Ensure that each instance of a model in Rails exists at most once in memory

Suppose I have a User class in my ActiveRecord model, and I store the user object inside a session when the user logs in. (i.e. session[:user] = user)
At some point in my code I might load the same user from the database and update it.
user = User.where(happy: false).first
user.happy = true
user.save
But then the user in the session is not up to date because session[:user] is a reference to another instance of User in memory.
If ActiveRecord query methods loaded instances of User from memory if possible (possibly with the ObjectSpace), this would fix the problem, but in order to avoid the XY problem, what is a better way of approaching this problem?
Is the best method to repeatedly refresh every object in the session when each controller runs? Should I avoid storing objects in the session in favor of object ID's?
Storing objects in the session store is a bad idea, as explained in this response.
So you're right with your guess of storing ids instead, that's the right way of doing it.

ASP MVC 4 Data passing

Im a new ASP MVC learner, Im working on a small pieces and getting stuck. The scenario is that user is creating new account in which accountID is automatically created by SQL. I want to pass this ID to the next step, let user input name and address. How would I pass this ID or any solution for this scenario? Think about passing data by TempData or Session but I'm not sure.
TempData will save the data until the end of your subsequent request.
There is also ViewBag/ViewData, which are really the same dictionary, are shorter lasting and they last only until the end of the current request.
Session lasts for as long as the user's session is open.
I believe TempData fits your bill, if "next step" means passing the data to a subsequent view, but if you need the data to be more durable, Session is the way to go.
I think you are approaching this incorrectly. You should have the user fill out all the information first, and then do all the registration, including creating the user and storing the data from the various steps all at the end.
If you do things the way you suggest, you will end up with partially registered users. They will create an account, and then maybe close their browser and never finish the rest. You will now have to write extra code to check all this every time they log in.
It's better to simply have your registration process do this all at once as an all or nothing process.
Just use the same process you would use for your login form. If the user is already entered in the database, you might as well treat them as a logged-in user that does not have a fully filled profile, yet.
In ASP.NET MVC, this would - in a lot of cases - be solved by setting the Forms Authentication cookie to the newly registered username
FormsAuthentication.SetAuthCookie(newUser.Logon, false);
I think that storing the user id in TempData or Session is the wrong scope for that. For all normal purposes, a user that just registered to your website can be treated as logged in after the record has been inserted in the user database.
You can try this way -
Create the user (username and password will be the values from your form):
WebSecurity.CreateUserAndAccount(username, password);
And then do this:
int userID = 0;
userID = WebSecurity.GetUserId(username);
Hope that helps.

MVC Cache Based on Variables in Session and Custom Security Logic

I have an application that shows many charts and tables using JQuery. Some of these charts are based on variables that are saved in the session (E.g. user added a value in another page and in the next page I am generating a chart, so the user request doesn't send any parameters)
I was looking around on the net and most of the solutions are based on
[OutputCache(Duration=60, VaryByParam="someParm")]
The problem is most of my request don't send parameters, they just use values that are in the session.
Is there any way to enable cache for these kinds of requests?
Edit: We have a complex security requirement that we couldn't use the default authorization attribute of MVC. We had to write logics based on the current user + the parameters sent to the action, so a method inside the action decides either to go ahead with the request or returns nothing. This makes caching very difficult because at the time OutputCache is executed we just have parameters, but identity object in the context is empty. As a result, if a user with admin privilege send a request for a and b and after him someone with minimum privilege send request for a and b, the second person will see the result because the action didn't run, but the value from the cache is used!
To solve this problem I used the getvarybyCustome. All this function does is to return user's group name which helps to create a more complex key. The person with minimum privilege in the last example will have different cache key (a,b,group_less) than the admin's request cache key (a,b,group_admin). However, getting's group name for each request is expensive as well, so I use Cache object to cache user's group, so at the beginning of the session the user's group is queried from AD and saved to cache, so for his/her later requests, his group name is retrieved from cache.
If something you can't achieve by VaryByParam then you can try VaryByCustom. See an example here
You could make a redirect of this request and send it to a new controller method sending the session parameters, by this way in a future implementation may be you use query string parameters instead of session and your code will work too.
You could make a method for conversion of this session parameters on a base class of all your controllers, to write the conversion once.

Redirect After Post in ASP.NET MVC

I am using the Redirect After Post pattern in my ASP.NET MVC application. I have
the following scenario:
User goes to /controller/index where he is asked to fill a form.
Form values are POSTed to /controller/calculate.
The Calculate action performs calculation based on input and instantiates a complex object containing the results of the operation. This object is stored in TempData and user is redirected to /controller/result.
/controller/result retrieves the result from TempData and renders them to the user.
The problem with this approach is that if the user hits F5 while viewing the results in /controller/result the page can no longer be rendered as TempData has been expired and the result object is no longer available.
This behavior is not desired by the users. One possible solution would be instead of redirecting after the POST, just rendering the results view. Now if the user hits F5 he gets a browser dialog asking if he wants to repost the form. This also was not desired.
One possible solution I thought of was to serialize the result object and passing it in the URL before redirecting but AFAIK there are some limitations in the length of a GET request and if the object gets pretty big I might hit this limitation (especially if base64 encoded).
Another possibility would be to use the Session object instead of TempData to persist the results. But before implementing this solution I would like to know if there's a better way of doing it.
UPDATE:
Further investigating the issue I found out that if I re-put the result object in TempData inside the /controller/result action it actually works:
public ActionResult Result()
{
var result = TempData["result"];
TempData["result"] = result;
return View(result);
}
But this feels kind of dirty. Could there be any side effects with this approach (such as switching to out-of-process session providers as currently I use InProc)?
Store it in the Session with some unique key and pass the key as part of the url. Then as long as the session is alive they can use the back/forward button to their heart's content and still have the URL respond properly. Alternatively, you could use the ASP cache, but I'd normally reserve that for objects that are shared among users. Of course, if you used the parameters to the calculation as the key and you found the result in the cache, you could simply re-use it.
I think redirect after post makes much more sense when the resulting Url is meaningfull.
In your case it would mean that all data required for the calculation is in the Url of /controller/result.
/controller/calculate would not do the calculation but /controller/result.
If you can get this done thinks get pretty easy: You hash the values required for the calculation and use it as the key for the cache. If the user refreshes he only hits the cache.
If you cant have a meaningfull url you could post to /controller/index. If the user hits F5 calculation would start again, but a cache with the hash as key would help again.
TempData is generally considered useful for passing messages back to the user not for storing working entities (a user refresh will nuke the contents of TempData).
I don't know of more appropriate place than the session to store this kind of information. I think the general idea is keep session as small as possible though. Personally I usually write some wrappers to add and remove specific objects to session. Cleaning them up manually where possible.
Alternatively you can store in a database in which you purge stale items on a regular basis.
I might adopt a similar idea to a lot of banks on their online banking sites by using one-time keys to verify all POSTs. You can integrate it into a html helper for forms and into your service layer (for example) for verification.
Let's say that you only want to post any instance of a form once. Add a guid to the form. If the form does not post back and the data is committed then you want to invalidate the guid and redirect to the GET action. If say the form was not valid, when the page posts back you need a new (valid) guid there in the form waiting for the next post attempt.
GUIDs are generated as required and added to a table in your DB. As they are invalidated (by POSTS, whether successful or not) they are flagged in the table. You may want to trim the table at 100 rows.. or 1000, depending on how heavy your app will be and how many rendered but not yet posted forms you may have at any one time.
I haven't really fine tuned this design but i think it might work. It wont be as smelly as the TempData and you can still adhere to the PRG pattern.
Remember, with PRG you dont want to send the new data to the GET action in a temp variable of some sort. You want to query it back from the data store, where it's now committed to.
As Michael stated, TempData has a single purpose -> store an object for one trip only and only one trip. As I understand it, TempData is essentially using the same Session object as you might use but it will automatically remove the object from the session on the next trip.
Stick with Session imho rather than push back in to TempData.

Resources