Is there a Rack middleware for using sessions without cookies?
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 sessioningservice. Cookies sent to the client for maintaining sessions will onlycontain an id reference. Only #get_session and #set_session arerequired 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 relatedcookie 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 sessionstate if it is used with a backend
- :renew (implementation dependent) will prompt the generation of a newsession 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 sessionid will be.
These options can be set on a per request basis, at the location ofenv['rack.session.options']. Additionally the id of the session can befound within the options hash at the key :id. It is highly notrecommended 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}" endend
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.rbRails.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.