SWFUpload, Rails, REST, and Sessions

To test TorqueBox, I’m always building little applications.

My latest little application involved uploading a user avatar image, which my app, using Paperclip, would shuffle to Amazon S3.

Form uploads are cool, but AJAXy Flash uploads are cooler.  So I grabbed SWFUpload from the conveniently-named sfwupload.org.

Integrating it into my application was a slight challenge, alas.

My User model directly holds the avatar_* columns Paperclip wants, in the form of

  • avatar_file_name
  • avatar_content_type
  • avatar_file_size
  • avatar_updated_at

To my feeble mind, this seems like a PUT on an existing User resource, if I want to be RESTful.

SWFUpload will gleefully do a POST, and I can add a _method=PUT to my parameters.  This is a concession Rails has made to the rest of the world, realizing that not everything supports every HTTP verb when you want it to.  So, Rails lets you do a POST, and tell it what verb you really meant through a parameter named _method.  Rails only respects _method, though, if it’s in the POST parameters.  So I can’t use the query-string to inject the psuedo PUT (or DELETE or …).

But SWFUpload (and most anything Flash) doesn’t participate in the containing browser session.  It doesn’t have my session cookie.  Or it doesn’t send it, if it does. Without session information, the server doesn’t really know who is sending this octet stream at the URL. So, how do I secure this update_user execution, if I can’t pass a session cookie?

SWFUpload has an extension to include every cookie as a parameter in the request.  It just adds name=value pairs for each cookie into the post parameters, alongside the _method and anything else you’ve specified.  So, if you had a cookie named session_id, it’d get added to the form paramters as session_id.

But with TorqueBox, the Rack session piggybacks upon the Java Servlet session, which, unfortunately, doesn’t know to inspect the POST parameters. By default, the servlet container is looking for a cookie.  I attempted to write a custom valve for the Tomcat request chain, but I only had reliable access to query parameters, not post parameters of the multipart form.

This makes sense, since the POST body is conceivably large, and is 100% the responsibility of the application. App servers don’t typically go mucking around in it on their own.  The application expects to be able to get an input stream for the POST body and ready starting from byte 0.  This means either massive buffering, or the simply that the app-server gives it to the app unmolested (and uninspected).

And since Rails expects _method to be in the POST parameters, but session cookie information could only be inspected if present in the URL, I came to an impasse, I thought.

To sum up:

  • Updating a User’s avatar is a PUT request against the User resource
  • SWFUpload can only do a POST
  • Rails lets us tunnel PUT through POST using _method=PUT
  • _method=PUT must be in the POST parameters, not URL bits.
  • SWFUpload can inject cookies request parameters.
  • SWFUpload can use either URL-based query parameters XOR POST parameters, but not both.
  • Session lookup can’t take advantage of POST parameters, only URL bits.

Though, without any other magic involved, our Java Servlet-based session can be divined by looking for a suffix on the URL of ;jsessionid=<id>.

For instance, you might have come across some Google-cached URLs such as

  • http://foo.com/products.jsp;jsessionid=918jk2j9j09j213

That’s the Java app-server’s method of passing session cookie information via URL.

It probably breaks 7 of the top 5 tenets of RESTful architectures, but it works.  If we can use this method for passing our session cookie during the upload, then we can successfully secure this interaction.

Ultimately, SWFUpload does a POST to the User resource URL, with ;jsession=<id> appended, and adds the _method=PUT to the POST parameters.  TorqueBox adds a url_suffix() method to the session object, which produces this full suffix.

Similar to this:


$upload_control.swfupload({
  upload_url: '#{url_for(user)}#{session.url_suffix}',
  post_params: {
    _method: 'PUT',
    authenticity_token: '#{form_authenticity_token}',
  },
  use_query_string: false});

Now we successfully pass session information, use CSRF-protection, and still communicate with a nicely RESTful controller on the back-end.

RESTful, Flashy, AJAXy, CSRF-protected and secure.  I’d call that a good day.

Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>