I’d hate to wake up some morning and find out that you weren’t you!
—Dr. Miles J. Binnell (Kevin McCarthy) in Invasion of the Body Snatchers (Allied Artists, 1956)
HTTP is a stateless protocol. Without the concept of a session (a concept not unique to Rails), there’d be no way to know that any HTTP request was related to another one. You’d never have an easy way to know who is accessing your application! Identification of your user (and presumably, authentication) would have to happen on each and every request handled by the server.1
Luckily, whenever a new user accesses our Rails application, a new session is automatically created. Using the session, we can maintain just enough server-side state to make our lives as web programmers significantly easier.
We use the word session to refer both to the time that a user is actively using the application, as well as to refer to the persistent hash data structure that we keep around for that user. That data structure takes the form of a hash, identified by a unique session id, a 32-character string of random hex numbers. When a new session is created, Rails automatically sends a cookie to the browser containing the session id, for future reference. From that point on, each request from the browser sends the session id back to the server, and continuity can be maintained.
The Rails way to design web applications dictates minimal use of the session for storage of stateful data. In keeping with the share nothing philosophy embraced by Rails, the proper place for persistent storage of data is the database, period. The bottom line is that the longer you keep objects in the user’s session hash, the more problems you create for yourself in trying to keep those objects from becoming stale (in other words, out of date in relation to the database).
This chapter deals with matters related to session use, starting with the question of what to put in the session.
Deciding what to store in the session hash does not have to be super-difficult, if you simply commit to storing as little as possible in it. Generally speaking, integers (for key values) and short string messages are okay. Objects are not.
There is one important integer that most Rails applications store in the session, and that is the current_user_id
. Not the current user object, but its id. Even if you roll your own login and authentication code (which you shouldn’t do), don’t store the entire User
(or Person
) in the session while the user is logged in. (See Chapter 14, Login and Authentication, for more information about keeping track of the current user.) The authentication system should take care of loading the user instance from the database prior to each request and making it available in a consistent fashion, via a method on your ApplicationController
. In particular, following this advice will ensure that you are able to disable access to given users without having to wait for their session to expire.
Here are some more general guidelines on storing objects in the session:
• They must be objects, serializable by Ruby’s Marshal API, which excludes certain types of objects such as a database connection and other types of I/O objects.
• Large object graphs may exceed the size available for session storage. Whether this limitation is in effect for you depends on the session store chosen and is covered later in the chapter.
• Critical data should not be stored in the session, since it can be suddenly lost by the user ending his session (by closing the browser or clearing his or her cookies).
• Objects with attributes that change often should not be kept in the session.
• Modifying the structure of an object and keeping old versions of it stored in the session is a recipe for disaster. Deployment scripts should clear old sessions to prevent this sort of problem from occurring, but with certain types of session stores, such as the cookie store, this problem is hard to mitigate. The simple answer (again) is to just not keep anything except for the occasional id in the session.
You used to be able to turn off the session, but as of Rails 3, applications that don’t need sessions don’t have to worry about them. Sessions are lazy-loaded, which means unless you access the session in a controller action, there is no performance implication.
The mechanism via which sessions are persisted can vary. Rails’ default behavior is to store session data as cookies in the browser, which is fine for almost all applications. If you need to exceed the 4KB cookies storage limit inherent in using cookies, then you can opt for an alternative session store. But of course, you shouldn’t be exceeding that limit, because you shouldn’t be keeping much other than an id or two in the session.
There are also some potential security concerns around session-replay attacks involving cookies, which might push you in the direction of using an alternative session storage.
The tools to switch over to storing sessions in the database are already built into Rails. The first step is to create the necessary migration, using a rake task provided for that very purpose, and run the migration to create the new table:
The second (and final) step is to tell Rails to use the new sessions table to store sessions, via a setting in config/initializers/session_store.rb
:
MyApplication::Application.config.session_store :active_record_store
That’s all there is to it.
If you are running an extremely high-traffic Rails deployment, you’re probably already leveraging memcache
in some way or another. memcache
is a remote-process memory cache that helps power some of the most highly trafficked sites on the Internet.
The memcache
session storage option lets you use your memcache
server as the repository for session data, and it is blazing fast. It’s also nice because it has built-in expiration, meaning you don’t have to expire old sessions yourself.
To use memcache, the first step is to modify Rails’ default session settings in config/initializers/session_store.rb
. At minimum, replace the contents of the file with the following:
MyApplication::Application.config.session_store :mem_cache_store
The Ruby-based memcache client gem, located at http://rubygems.org/gems/memcache-client is supposed to ship with Rails. If your server startup crashes and complains that it can’t find the memcache file to load, manually add memcache_client
to your Gemfile. If you’re feeling particularly geeky, you may try installing one of the memcache clients with native bindings, such as http://github.com/ninjudd/memcache or http://blog.evanweaver.com/files/doc/fauna/memcached.
The session_store
method support options as well.
memcache_options = {
:c_threshold => 10_000,
:compression => true,
:debug => false,
:namespace => ":app-#{Rails.env}",
:readonly => false,
:urlencode => false
}
MyApplication::Application.config.session_store :mem_cache_store,
memcache_options
In February 2007, core-team member Jeremy Kemper made a pretty bold commit to Rails. He changed the default session storage mechanism from the venerable PStore
to a new system based on a CookieStore
. His commit message summed it up well:
Introduce a cookie-based session store as the Rails default. Sessions typically contain at most a user id and flash message; both fit within the 4K cookie size limit. A secure hash is included with the cookie to ensure data integrity (a user cannot alter his user id without knowing the secret key included in the hash). If you have more than 4K of session data or don’t want your data to be visible to the user, pick another session store. Cookie-based sessions are dramatically faster than the alternatives.
I describe the CookieStore
as controversial because of the fallout over making it the default session storage mechanism. For one, it imposes a very strict size limit, only 4K. A significant size constraint like that is fine if you’re following the Rails way, and not storing anything other than integers and short strings in the session. If you’re bucking the guidelines, well, you might have an issue with it.
Lots of people have complained about the inherent insecurity of storing session information, including the current user information on the user’s browser. However, there are security measures in place that make the cookie store hard to crack open and exploit. For instance, you’d need to be able to compromise SHA1, which is somewhat difficult to do.
But let’s say you want different security,2 you can easily override the existing hashing code by setting it to any other digest provided by OpenSSL:
ActionController::Base.session_options[:digest] = SHA512
Another problem with cookie-based session storage is its vulnerability to replay attacks, which generated an enormous message thread on the rails-core mailing list. S. Robert James kicked off the thread3 by describing a replay attack:
• Example:
1. User receives credits, stored in his session.
2. User buys something.
3. User gets his new, lower credits stored in his session.
4. Evil hacker takes his saved cookie from step 1 and pastes it back in his browser’s cookie jar. Now he’s gotten his credits back.
• This is normally solved using something called nonce. Each signing includes a once-only code, and the signer keeps track of all of the codes, and rejects any message with the code repeated. But that’s very hard to do here, since there may be several app servers serving up the same application.
• Of course, we could store the nonce in the DB, but that defeats the entire purpose!
The short answer is: Do not store sensitive data in the session. Ever. The longer answer is that coordination of nonces across multiple servers would require remote process interaction on a per-request basis, which negates the benefits of using the cookie session storage to begin with.
The cookie session storage also has potential issues with replay attacks that let malicious users on shared computers use stolen cookies to log in to an application that the user thought he or she had logged out of. The bottom line is that if you decide to use the cookie session storage on an application with security concerns, please consider the implications of doing so carefully.
If you’re using ActiveRecordStore
, you can write your own little utilities for keeping the size of your session store under control. Listing 13.1 is a class that you can add to your /lib
folder and invoke from the production console or a script whenever you need to do so.
Listing 13.1. SessionMaintenance class for cleaning up old sessions
This section is about using cookies, not the cookie session store. The cookie container, as it’s known, looks like a hash, and is available via the cookies
method in the scope of controllers. Lots of Rails developers use cookies to store user preferences and other small nonsensitive bits of data. Be careful not to store sensitive data in cookies because they can be read by users.
Contrary to what at least some developers might expect, the cookies
container is not available by default in view templates or helpers. If you need to be able to access cookies in your helpers or views, there is a simple solution. Simply declare cookies
to be a helper method:
class MyController < ActionController::Base
helper_method :cookies
The cookie container is filled with cookies received along with the request, and sends out any cookies that you write to it with the response. Note that cookies are read by value, so you won’t get the cookie object itself back, just the value it holds as a string (or as an array of strings if it holds multiple values).
To create or update cookies, you simply assign values using the brackets operator. You may assign either a single string value or a hash containing options, such as :expires
, which takes a number of seconds before which the cookie should be deleted by the browser. Remember that Rails convenience methods for time are useful here:
I find the :path
options useful in allowing you to set options specific to particular sections or even particular records of your application. The :path
option is set to '1'
, the root of your application, by default.
The :domain
option allows you to specify a domain, which is most often used when you are serving up your application from a particular host, but want to set cookies for the whole domain.
Cookies can also be written using the :secure
option, and Rails will only ever transmit them over a secure HTTPS connection:
Finally, you can delete cookies using the delete
method:
cookies.delete :list_mode
Writing cookies to the response via the cookies.permanent
hash automatically gives them an expiration date 20 years in the future.
cookies.permanent[:remember_me] = current_user.id
Writing cookies to the response via the cookies.signed
hash generates signed representations of cookies, to prevent tampering of that cookie’s value by the end user. If a signed cookie was tampered with a ActiveSupport::MessageVerifier::InvalidSignature
exception will be raised when that cookie is read in a subsequent request.
cookies.signed[:remember_me] = current_user.id
Deciding how to use the session is one of the more challenging tasks that faces a web application developer. That’s why we put a couple of sections about it right in the beginning of this chapter. We also covered the various options available for configuring sessions, including storage mechanisms and methods for timing out sessions and the session life cycle. We also covered use of a closely related topic, browser cookies.
3.15.137.59