We build Web & Mobile Applications.

< All Articles

Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue!

It is one of life’s strange coincidences that in the week where Rack middleware was brought firmly into the spotlight in Railsland thanks to the introduction of Metal and the continuing transition of Rails to a Rack application that I finally had a need to write some middleware of my own. Up until now I’ve had a rough understanding of how Rack works and how middleware can used to provide customised processing in a web request but I haven’t actually needed to use it for anything.

Fun and games

The application I’ve been working on this month uses FancyUpload to provide a snazzy UI for uploading image files that are then processed and stored using Paperclip and ImageMagick. FancyUpload is a JavaScript and Shockwave Flash based solution that gives you all kinds of goodness like multiple file uploads, progress bars and status information. Unfortunately getting FancyUpload, as well as any other form of flash based uploader such as SWFUpload, to play nice with Rails requires some work.

I won’t delve too deeply into the various problems as much has already been written about them, along with possible solutions. Instead here’s some background reading:

Unfortunately the CGI patching approach suggested in the above blogs presented a problem for me: I’m running on edge Rails which over the last couple of weeks has pretty much shed all of its CGI baggage so it’s unlikely the patches would work. What to do?

Bring on the middleware!

My first thought was “surely this is something middleware can help out with” and so a quick Google yielded exactly that, the only problem being it was for Merb.

Here’s the code (by Angel Pizarro):

module Merb
  module Rack
    class SwfSetSessionCookie < Merb::Rack::Middleware
      # :api: private
      def initialize(app, session_key = '_session_id')
        @session_key = session_key
      # :api: plugin
      def call(env)
        if env["HTTP_USER_AGENT"] =~ /Adobe Flash/
          prms = Merb::Parse.query(env['QUERY_STRING'])
          env['HTTP_COOKIE'] = [@session_key,prms[@session_key]].join('=').freeze

What this does is check the request environment for Flash in the HTTP_USER_AGENT header, if a match is found then the request query string is parsed for session data (the session_key is passed to the middleware when it is initialised) which is then forced into the HTTP_COOKIE header so that when the request reaches Merb it treats the session data just like it would in any other request.

I’m pleased to say that converting this into middleware that will work with Rails (and in theory any other Rack application) was extremely simple, especially once I discovered that the Rack::Utils library provides an implementation of the Merb::Parse.query method. The only functional change I needed to make was to the HTTP_USER_AGENT regular expression so that it would work with newer versions of Flash. Here’s the code:

require 'rack/utils'

class FlashSessionCookieMiddleware
  def initialize(app, session_key = '_session_id')
    @app = app
    @session_key = session_key

  def call(env)
    if env['HTTP_USER_AGENT'] =~ /^(Adobe|Shockwave) Flash/
      params = ::Rack::Utils.parse_query(env['QUERY_STRING'])
      env['HTTP_COOKIE'] = [@session_key, params[@session_key] ].join('=').freeze unless params[@session_key].nil?

Integration with Rails

Getting the middleware to work with Rails involved putting the above code in my newly created RAILS_ROOT/app/middleware folder in a file snappily named flash_session_cookie_middleware.rb. At the moment there doesn’t appear to be a convention for where these files should live, but this makes sense to me as the new Metal endpoints will live in RAILS_ROOT/app/metal. The middleware folder then needs adding to the load path in environment.rb:

config.load_paths += %W( #{RAILS_ROOT}/app/middleware )

Rails provides a new config.middleware.use setting that can be used in environment.rb to specify middleware classes, however because I wanted to pass the session key to the constructor I decided to do the following in my RAILS_ROOT/config/initializers/session_store.rb initializer instead (session store configuration was moved to an initializer in this commit):

ActionController::Base.session = {
  :session_key => '_my_app_session',
  :secret => '--blah--' # Real key removed to protect the innocent

ActionController::Dispatcher.middleware.use FlashSessionCookieMiddleware, ActionController::Base.session_options[:session_key]

The first part of the initializer sets the session key and secret as normal, the next part calls ActionController::Dispatcher.middleware.use to register the FlashSessionCookieMiddleware class passing it the session key as a parameter.

The final step is to pass the session data and authenticity token as URL parameters so that they can be picked up by the middleware. In our application we have an assets controller, so our upload form_for originally looked like this:

form_for(@asset, :multipart => true)

This was changed like so:

form_for(@asset, :url => new_asset_path_with_session_information, :multipart => true)

And in our AssetsHelper module a new_asset_path_with_session_information helper was added:

def new_asset_path_with_session_information
  session_key = ActionController::Base.session_options[:session_key]
  new_asset_path(session_key => cookies[session_key], request_forgery_protection_token => form_authenticity_token)

Our Javascript code then uses the form URL when initialising the FancyUpload component, when a file is uploaded it will use a HTTP POST to a URL like this:


The middleware will take the data in the _my_app_session parameter and convert it back into a cookie, and Rails will use the authenticity_token parameter to verify the request. So as far as Rails is concerned this is a regular POST request using the current session with a valid authenticity token: it has no clue that this really came from the FlashUpload component.

This is just the beginning

This has been my first experiment in writing Rack middleware and I think it is a great example of how it can be used to solve problems in a clean, straight-forward way without having to resort to the hackery of days gone by. My middleware shows some really very simple functionality, however this should be enough to make you want to find out what more can be achieved. A good place to start is the rack-contrib repository on GitHub and the Rack development Google group.

Have fun, and try not to get too carried away!

An update for Rails edge: March 2009

Thanks to Saimon Moore for pointing out that Rails edge has renamed the :session_key option to :key. If you’re running on edge then you’ll need to make the following changes:

  1. In the RAILS_ROOT/config/initializers/session_store.rb initializer:

    ActionController::Base.session = {
      :key => '_my_app_session',
      :secret => '--blah--' # Real key removed to protect the innocent
    ActionController::Dispatcher.middleware.use FlashSessionCookieMiddleware, ActionController::Base.session_options[:key]
  2. And in the AssetsHelper#new_asset_path_with_session_information method:

    def new_asset_path_with_session_information
      session_key = ActionController::Base.session_options[:key]
      new_asset_path(session_key => cookies[session_key], request_forgery_protection_token => form_authenticity_token)

An update for Rails 2.3.2: June 2009

David North kindly emailed us with the necessary changes to allow our Flash cookie to work with Rails 2.3.2. Without this change you will likely still get InvalidAuthenticityToken exceptions.

In the RAILS_ROOT/config/initializers/session_store.rb initializer we need to inject our middleware before the new Rails cookie middleware. To do this change the ActionController::Dispatcher.middleware.use call to this:

ActionController::Dispatcher.middleware.insert_before(ActionController::Session::CookieStore, FlashSessionCookieMiddleware, ActionController::Base.session_options[:key])

You can double check your middleware order is correct using the rake middleware task on the command line. The output should look something like this:

use Rack::Lock
use ActionController::Failsafe
use ActionController::Reloader
use FlashSessionCookieMiddleware, "_my_app_session"
use ActionController::Session::CookieStore, <Proc:0x02445eb0@(eval):8>
use ActionController::RewindableInput
use ActionController::ParamsParser
use Rack::MethodOverride
use Rack::Head
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
run ActionController::Dispatcher.new

As you can see our FlashSessionCookieMiddleware appears before ActionController::Session::CookieStore which means our cookie fiddling will take place before Rails tries to use it.

If you want to find out more on Rails and Rack integration you should take a look at the new Rails on Rack guide.

A better way of inserting middleware: July 2009

Tung Nguyen contacted us with a better, more generic way of adding the middleware to your stack, meaning you no longer have to double check where to insert it:

ActionController::Dispatcher.middleware.insert_before(ActionController::Base.session_store, FlashSessionCookieMiddleware, ActionController::Base.session_options[:key])

A warning about session fixation

Trevor Rowe contacted us with a warning about session fixation using this approach:

I’ve noticed a nasty little side-effect using the methods described in this blog post. The flash uploader will fixate on the session you give it. If your server tries to modify the session during the upload processes the changes to the session are lost (or rather ignored).

A little longer explanation:

When constructing the url with the session key and session in the query string, it saves that off. Each successive request to your upload action will receive the same session. Because flash is not sharing the session cookies with your browser, changes made to that session in another tab get lost. The browser will delete the session cookie, but flash is unaware. If you refresh the page where your session cookie is passed to the flash uploader then it will get the new url with the new embedded-session query string.

If this behavior is unacceptable, the only solution I can come up with involves send the new session back with every upload response, and then have javascript grab the new session and give a new url to the flash uploader before it makes its next upload request. Take care to make sure you get the right session. If you just ask for coookies[session_key] in your controller you will get the old session. You will need to after you make all of the modifications to session marshall it down into the string that would normally be sent as the cookie string. You could also just add to the rack middleware and pull the new session out of its headers.

Updated on 07 February 2019
First published by Rob Anderton on 22 December 2008
© Rob Anderton 2019
"Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue!" by Rob Anderton at TheWebFellas is licensed under a Creative Commons Attribution 4.0 International License.