Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a Rack middleware for using sessions without cookies?

The provided session management middleware that comes with Rack are all based on cookies for identifying the user. Since I'm developing an api, I would rather pass the session-id explicitly as a query string parameter. Looking at the code base, it doesn't seem that this use case was taken into consideration, as all the session middlewares extend from a common class, that reads/writes to cookies.

So my question is - Is there a project that maintains an alternative Rack middleware or a monkey patch for racks built-in middleware's, that will allow me to maintain the session-id over the query string, rather than a cookie store?

like image 579
troelskn Avatar asked Mar 19 '13 12:03

troelskn


People also ask

How does Rack middleware works?

Rack middleware is a way to filter a request and response coming into your application. A middleware component sits between the client and the server, processing inbound requests and outbound responses, but it's more than interface that can be used to talk to web server.

Is rails a Rack app?

2.1 Rails Application's Rack Objectapplication is the primary Rack application object of a Rails application. Any Rack compliant web server should be using Rails.

What is rack application?

Rack is a modular interface between web servers and web applications developed in the Ruby programming language. With Rack, application programming interfaces (APIs) for web frameworks and middleware are wrapped into a single method call handling HTTP requests and responses.


2 Answers

Rack can use custom session ID items instead of cookies:

require 'rack/session/abstract/id'

The Rack documentation may be a helpful place to start your search. I believe you're looking for the "skip" option (or "defer" option).

Docs:

ID sets up a basic framework for implementing an id based sessioning service. Cookies sent to the client for maintaining sessions will only contain an id reference. Only #get_session and #set_session are required to be overwritten.

All parameters are optional.

  • :key determines the name of the cookie, by default it is 'rack.session'
  • :path, :domain, :expire_after, :secure, and :httponly set the related cookie options as by Rack::Response#add_cookie
  • :skip will not a set a cookie in the response nor update the session state
  • :defer will not set a cookie in the response but still update the session state if it is used with a backend
  • :renew (implementation dependent) will prompt the generation of a new session id, and migration of data to be referenced at the new id. If :defer is set, it will be overridden and the cookie will be set.
  • :sidbits sets the number of bits in length that a generated session id will be.

These options can be set on a per request basis, at the location of env['rack.session.options']. Additionally the id of the session can be found within the options hash at the key :id. It is highly not recommended to change its value.

Is Rack::Utils::Context compatible.

Not included by default; you must require 'rack/session/abstract/id' to use.

Source:

  class ID
    DEFAULT_OPTIONS = {
      :key =>           'rack.session',
      :path =>          '/',
      :domain =>        nil,
      :expire_after =>  nil,
      :secure =>        false,
      :httponly =>      true,
      :defer =>         false,
      :renew =>         false,
      :sidbits =>       128,
      :cookie_only =>   true,
      :secure_random => (::SecureRandom rescue false)
    }

I hope this gives you a lead... when you learn more, can you share your results here?

Edit:

The magic trick is to combine options :cookie_only => false with :defer => true. Of course, the standard Rack::Session::Cookie doesn't make much sense here, so you could do:

use Rack::Session::Pool, :cookie_only => false, :defer => true

Interestingly you can alter the options in run time. In my use case, I actually need to support a traditional cookie-based mechanism alongside the explicit parameter-passing style, so I have done the following:

class WebApp < Sinatra::Base

  configure do
    use Rack::Session::Pool, :key => 'session_id'
  end

  before do
    # Switch to parameter based session management if the client is an ios device
    if env['HTTP_USER_AGENT'] =~ /iOS/
      session.options[:cookie_only] = false
      session.options[:defer] = true
    end
  end

  get '/' do
    session[:user_id] ||= nil # This triggers a session-write, giving us a valid session-id
    body "session_id=#{session.id}"
  end
end
like image 164
joelparkerhenderson Avatar answered Oct 07 '22 22:10

joelparkerhenderson


If you want to eliminate usage of cookies in your API application, but still want to manage sessions. For instance, in my case session identifier came from token. You need to redefine method extract_session_id to extract you session identifier from received token. This method have to be redefined on your session store class, because Rack::Session::Abstract::ID provides default implementation, based on cookies. It is called from Rack::Session::Abstract::ID#current_session_id method which calls #id method on session object, usually represented by Rack::Session::Abstract::SessionHash instance. And inside this SessionHash#id method #extract_session_id from your session store finally called.

I suppose it would be simpler to have session identifier extractor configured independently, but desire to store session identifiers in cookies along with session data lead to that tricked design.

As well it is a bit weird to see cookie interaction in Rack::Session::Abstract::ID class in method #commit_session and #set_cookie. So to be completely sure that no cookie will be set you could redefine #set_cookie method on your store as well. The same goal probably can be achived by setting cookie_only: false, defer: true for your session storage middleware, as mentioned in one of the answer here, but I have not checked this.

Definitely you need to modify your middleware stack to exclude all middlewares which interact with cookies and likely browser specific. On default middleware stack of rails it can be look like this:

# config/application.rb
[
  Rack::MethodOverride, # browser specific
  ActionDispatch::Cookies,
  ActionDispatch::Flash
].each do |middleware|
  config.middleware.delete(middleware)
end

As well you definitely need to replace session store to something which store information on server side, like redis, for example:

# config/initializers/session_store.rb
Rails.application.config.session_store ::Custom::Session::TokenRedisStore

In my case, ::Custom::Session::TokenRedisStore inherits ::RedisSessionStore and redefined all methods mentioned above. ::RedisSessionStore contained in redis-session-store gem. So, obviously you need to add it to you Gemfile if you going to use it.

I do that for Rails 4.2.x, but same approach can be adopbed to any framework as Rack everywhere the same.

like image 28
Zeke Fast Avatar answered Oct 08 '22 00:10

Zeke Fast