Cookie Sessions

This works like the thing in Rails 2.0 by a similar name. Cookie Sessions takes all the stuff you put in the @state session variable and turns it in to a string, base 64 encodes it, then signs the whole mess with a super duper secret hashed together with the data, sending it off to the user as a cookie! The fun thing about this is you don't need a database or even a filesystem to use it! All the information is stored with the user, and thanks to the secret thing they don't know and can't find out (don't tell anyone what it is!) they can see what’s in their session, but they can't change it, or the server will totally ignore them and start them off with a brand new empty session, effectively logging them out in most situations.

require 'base64'

module Camping
  module CookieSessions
    # this thingy wraps around the main thingy and puts sessions in like magic, ooooOOOOOooooh! Spooky!
    def service(*a)
      if @cookies.identity
        blob, secure_hash = @cookies.identity.to_s.split(':', 2)
        blob = Base64.decode64(blob)
        data = Marshal.restore(blob)
        data = {} unless secure_blob_hasher(blob).strip.downcase == secure_hash.strip.downcase
      else
        blob = ''
        data = {}
      end
      
      app = self.class.name.gsub(/^(\w+)::.+$/, '\1')
      @state = (data[app] ||= Camping::H[])
      hash_before = blob.hash
      return super(*a)
    ensure
      data[app] = @state
      blob = Marshal.dump(data)
      unless hash_before == blob.hash
        secure_hash = secure_blob_hasher(blob)
        @cookies.identity = Base64.encode64(blob).gsub("\n", '').strip + ':' + secure_hash
        # hack to make cookies update
        @headers['Set-Cookie'] = @cookies.map { |k,v| "#{k}=#{C.escape(v)}; path=#{self/"/"}" if v != @k[k] } - [nil]
      end
    end
    
    # You can override this with whatever hashing function you think is awesome enough, don't use MD5 though! It stinks!
    def secure_blob_hasher(data)
      require 'digest'
      require 'digest/sha2'
      Digest::SHA512::hexdigest(self.class.module_eval('@@state_secret') + data)
    end
  end
end

So go, take that code, and pop it in a file, call it cookie_sessions.rb and require it or paste the whole lot up the top of your camping app if you like, and change out your old databasey sessions for cookie sessions by using this next thing instead of 'include Camping::Sessions'.

module Camping (or whatever you put after Camping.goes :...)
  include Camping::CookieSessions
  @@state_secret = "You want a really really long string of rubbish nobody could ever ever guess! Don't tell anyone! Not even your girlfriend or dog!"
end

And make darned sure that string is crazy! Don't even use words if you can, or use some joke that's so not funny nobody would ever think of it, but do it in leet speak or something just for good measure! And that's that, you have shiny new sessions now which don't polute your filesystem or database and work just like the old ones!

A word of technical warning however: Don't store the all of wikipedia in @state when you use this! It won't fit in a cookie! These things are only about 4kb big, and the signature hash in the default secure_blob_hasher method takes up half a kilobyte on it's own. The base64 encoding then add's 33% to the size of the marshalled data, which itself add's size with stuff to reconstruct original objects. In short, just store a user id or something, maybe a few little things like that. Session's aren't really the way to go for storing big stuff even when they can, keep it little! :)