Notebook entries on January 2009

9 January 2009

rack-facebook, a new Rack middleware to parse facebook parameters

During our recent work on the current project we're building now using the sinatra web framework, we needed a simple and effective way to treat Facebook requests. The minimum set of features that the solution should cover would be:

  • It should check that the facebook signature provided at fb_sig is valid. That is, it should take all the Facebook parameters (those who start with a fb_sig_ prefix), concatenate them in order with the secret key, and calculate a MD5 hash. If the resulted hash isn't the same that the passed in at the fb_sig parameter, it should return a 400 Bad Request.
  • It should convert Facebook parameters to Ruby objects when possible: 0/1 flags to false/true, timestamps to Time instances, and comma-separated friends list to an array of ids.
  • It should convert the Facebook POST request method to the appropiate http method. Facebook always use a POST to communicate with a web application, but it sends the original client method at the fb_sig_request_method parameter. The environment should be modified so it uses the original client method on the request.
  • It should be easy to disable it when necessary. Ideally, it should allow to skip all the previous processes according to an arbitrary set of rules. For example, it could be disabled when the request URI matches a regular expression, or when the client IP address has a certain value.
  • It should execute the previous steps as soon as possible, to discard invalid requests quickly without overload the application.

For all these reasons, I decided to build a Rack middleware that takes care of those features, and just give the application valid requests all the time. Its name is rack-facebook, and to install it just do:

gem install carlosparamio-rack-facebook --source

On your or application:

require 'rack/facebook' use Rack::Facebook, :app_name => "My Application", :application_secret => "SECRET", :api_key => "APIKEY"

Without passing a block, the middleware will check the signature for all the incoming requests, denying those which doesn't have the appropiate shape. You can specify a block to filter when to apply the middleware. The block will receive the Rack environment as an argument, and it must return a value that evaluates to true to tell the middleware that it must be runned. For example, you can run the middleware only when the URI starts with /facebook_only:

use Rack::Facebook, :app_name => "My Application", :application_secret => "SECRET", :api_key => "APIKEY" do |env| env['REQUEST_URI'] =~ /^\/facebook_only/ end

And that's it! The Facebook parameters are parsed to Ruby objects, so given a request like:

curl -X POST --data "fb_sig_locale=en_US&fb_sig_request_method=GET&fb_sig=8773c0e980961240de13bf482fd3999d&fb_sig_in_new_facebook=1&fb_sig_position_fix=1&fb_sig_in_canvas=1&fb_sig_added=0&fb_sig_api_key=1234567890abcdef1234567890abcdef&fb_sig_time=1231494529.1224" http://yourappurl:port

Will be converted to something like:

{"fb_sig_locale"=>"en_US", "fb_sig_request_method"=>"GET", "fb_sig"=>"8773c0e980961240de13bf482fd3999d", "fb_sig_in_new_facebook"=>true, "fb_sig_position_fix"=>true, "fb_sig_in_canvas"=>true, "fb_sig_added"=>false, "fb_sig_api_key"=>"1234567890abcdef1234567890abcdef", "fb_sig_time"=>Fri Jan 09 10:04:59 +0100 2009}

And the HTTP method that the application will receive will be GET instead of POST, because of the fb_sig_request_method parameter.