Chapter 13. Session Management

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.

13.1 What to Store 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.

13.1.1 The Current User

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.

13.1.2 Session Use Guidelines

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.

13.2 Session Options

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.

13.3 Storage Mechanisms

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.

13.3.1 Active Record Session Store

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:

image

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.

13.3.2 Memcache Session Storage

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


Note

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

13.3.3 The Controversial CookieStore

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.

OpenSSL Digests

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

Replay Attacks

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.

13.3.4 Cleaning Up Old Sessions

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

image

13.4 Cookies

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

13.4.1 Reading and Writing 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:

image

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.

image

Cookies can also be written using the :secure option, and Rails will only ever transmit them over a secure HTTPS connection:

image

Finally, you can delete cookies using the delete method:

cookies.delete :list_mode

Permanent Cookies

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

Signed Cookies

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

13.5 Conclusion

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.137.59