Browser session cookies and Dalli

Ben Lindsey ·

A while back one of our clients requested that Rails sessions be stored in session cookies (i.e. a cookie that will expire when the browser is closed). This was for security reasons so your cookie couldn’t be read off the disk and replayed on another browser/machine.

We were using memcached to store our sessions and dalli as the client. Dalli is an excellent library for the memcached binary protocol that has support for Rails session storage. If you aren’t using it for memcached in Rails, you should be.

The setup looked like this:

config/memcached.yml

development:    [ localhost ]
test:           [ localhost ]
production: [ prodmem01 prodmem02 ]

config/initializers/session_store.rb

require 'action_dispatch/middleware/session/dalli_store'
memcached_config = YAML.load_file(File.join(Rails.root, 'config', 'memcached.yml'))
Rails.application.config.session_store :dalli_store,
  :memcache_server => memcached_config[Rails.env],
  :namespace => 'sessions',
  :key => '_session',
  :expire_after => 15.minutes

Cookies are perisistent and will expire after 15 minutes from the browser and from memcached. If you close your browser, your session will stay alive for 15 minutes. Not good. We want a session cookie that expires when you close the browser and a key that expires from memcached in 15 minutes.

After digging around the dalli documentation, I came across :expires_in which is supposed to set the expiration ttl on the server. So I changed :expire_after to :expires_in and fired up Web Developer and tailed my memcached log. Now I had a session cookie that would expire immediately but no ttl set in memcached. Doh. Filling up memcached with unused sessions is not a good idea.

So I checked out the DalliStore code and found that when :expire_after is blank, it defaults ttl to 0 which overrides :expires_in in the Dalli::Client. Time for a monkey patch.

cache = Dalli::Client.new(memcached_config[Rails.env], :expires_in => 15.minutes.to_i)
def cache.set(key, value, ttl=nil, options=nil)
perform(:set, key, value, @options[:expires_in], options)
end

Rails.application.config.session_store :dalli_store,
  :cache => cache,
  :namespace => 'sessions',
  :key => '_session'

This will create a single instance of a Dalli::Client and redefine its set method to always override the ttl. That way no other Dalli::Client will be affected. Gotta love ruby metaprogramming. The DalliStore uses the :cache config parameter as its Dalli::Client. Restart Rails and voila! Now we have a session cookie and an expiration of 15 minutes in memcached.

Ben Lindsey
Ben Lindsey

Runner; Chef; Trancer; Traveler; Technologist; Agile evangelist. I'm a firm believer in that which you measure will surely improve. Currently I am working on how to test drive and scale a node.js application in the cloud.