HellOnline

Icon

Eran's blog

Sessions on Rails without Cookies

Developing for the mobile web feels like being back in 1998. Standardization is lacking although with XHTML-MP we’re doing much better than back in the bad ole days. Still, browsers are many and varied and have different levels of support for different features. Even something so basic like cookies, which we’ve all been taking for granted for so long is no longer guaranteed.

Some phone browsers support cookies. Some get cookie support handled by the gateway. Some have persistent cookies, some do not. How do we get around that? Just like we did before – URL rewriting.
http://example.com/?_session_id=2b34560e1cfd62a265b243accc493c68 much?

Rails already has some support for sessions without cookies. Check out the CGI library (where Rails’ sessions come from) and you’ll see that it knows how to get the session_id from the request parameters. All that means is we have to get them in there. I found a basic URL rewriting idea here. which works by overriding #url_for. I plugged the new method into ApplicationHelper and it almost worked. The only thing missing was support for named route URLs (e.g. foo_url). On my quest to get those working I stumbled on default_url_options and this, much simpler, solution:


def default_url_options(options)
{‘_session_id’ => session.session_id}
end

Really, that’s all you need. Almost. I was having some problems with forms that looked like:

For some reason, the combination of a POST and passing _session_id as a query parameter did not go over well. Time to override #form_for:


def form_for(object_name, *args, &proc)
raise ArgumentError, "Missing block" unless block_given?
key_name = get_session_key_name

# see if a cookie with this key has been set.
if request.cookies.has_key?(key_name) then
return super
end

options = args.last.is_a?(Hash) ? args.pop : {}
concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}), proc.binding)
# This is what you’re looking for right here:
concat(hidden_field_tag(key_name, session.session_id), proc.binding)
fields_for(object_name, *(args << options), &proc)
concat('', proc.binding)
end

That hidden field will get your session_id across. It feels like an ugly hack and I bet there’s a better way to do this but for now, it’s what works.

Advertisements

Filed under: Ruby on Rails

4 Responses - Comments are closed.

  1. Great post, Eran – thank you. A couple of notes:

    1. Watch the curly quotes in the code blocks above – if you simply copy and paste, you’ll need to replace those with standard ticks. (A problem with WordPress?)

    2. I ended up putting this in application.rb to support cookieless sessions ONLY IF client does not accept cookies:

    def default_url_options(options)
    if cookies[“_session_id”] = nil
    # append session var to URLs if client not accepting cookies
    {“_session_id” => session.session_id}
    end
    end

    Cheers,
    Ron

  2. Jim says:

    In Ron’s code “= nil” seems to be a typo. I just changed it to use blank? instead.

    def default_url_options(options)
    if cookies[“_kkj_sid”].blank?
    # append session var to URLs if client not accepting cookies
    {“_kkj_sid” => session.session_id}
    end
    end

    -Jim

  3. cwittenb says:

    I don’t want to be picky, but still the default_url_options(options) append a session_id to the url at home_path, when the user visits it for the first time, as no cookie is set at this time – not so nice.
    What about:

    def default_url_options(options)
    cookies[:_uaf_session_id] ||= { :value => “true”, :expires => 10.seconds.from_now }
    { :_uaf_session_id => session.session_id } unless cookies[:_uaf_session_id]
    end

  4. cwittenb says:

    … ah and another thing that didn’t work well were ajax calls,
    they passed the url correct with session_id appended, but somehow still initiated a new session, so I ended up using the method as:

    def default_url_options(options)
    cookies[:_session_id] ||= { :value => “true”, :expires => 10.seconds.from_now }
    { :_session_id => (request.xhr? ? params[:_session_id] : session.session_id) } unless cookies[:_session_id]
    end

    as said above: “It feels like an ugly hack and I bet there’s a better way to do this but for now, it’s what works.”

    … anyway thanks for this illuminating post.

%d bloggers like this: