Validate a Facebook JavaScript SDK cookie with Ruby


UPDATE: The Facebook API has changed since this article was posted. The code below will no longer work with the cookies provided by Facebook (which now looks like “fbsr_#{@fb_app_id}”). There is some sample code in the comments below that is working for me now (Aug 26, 2012).

You’ve authenticated a user using the Facebook JavaScript SDK and now you want your server-side code to know about the user and their login status. The JavaScript SDK makes this possible by creating a cookie for the user which is sent with each request to your application. However, if you are using Facebook to control access in your application, you’ll want to make sure the cookie is valid, and hasn’t been tampered with by the user. It would be way too easy for the user to use a tool like FireCookie to change values in the cookie to indicate they are a different user, or to lie about their Facebook authentication status. To solve this problem, the cookie includes a signature based on a secret shared between your application and Facebook, which your server-side code can validate. Unfortunately, the only documentation I could find for the cookie verification algorithm was some sample PHP code. This wasn’t very helpful to me since a) I don’t know PHP, and b) I’m writing my application in Ruby (Sinatra!). I didn’t have any luck finding a Ruby implementation, so I’m throwing mine out there so I can find it in the future, and maybe someone else can use it.

Facebook will provide you with an Application ID and Application Secret when you register your application. This sample code will assume they are available in instance variables @fb_app_id and @fb_app_secret.

First, get the value of the Facebook cookie. It is named “fbs__your_application_id_”. In Sinatra, I would get the cookie value like this:

cookie = request.cookies["fbs_#{@fb_app_id}"]

The cookie value will look something like this (note the double quotes on the ends are included in the value):

"access_token=abcdefg1235&secret=j8675309&session_key=453xyz&sig=3d4f461d9f9e958c344a1671fbb3f931&uid=1001"

(The example cookie value was generated using @fb_app_secret = “password1234”)

The next step is to turn the key/value pairs into a hash:

def cookie_values(cookie)
  cookie[1..-2].split('&').reduce({}) do |hash, val|
    parts = val.split('=')
    hash[parts[0]] = parts[1]
    hash
  end
end

fb_info = cookie_values(cookie)

(Technically you can validate the cookie without building the hash, but the hash will be useful when you want to later retrieve values such as the access_token or uid)

Now calculate the signature:

def signature(info)
  Digest::MD5.hexdigest(info.keys.reject{|k| k=="sig"}.sort.reduce(""){|out,k| "#{out}#{k}=#{info[k]}"}.to_s + @fb_app_secret)
end

And compare it with the signature in the cookie. If the values are the same, you can trust the cookie has not been manipulated.

valid = fb_info[“sig”] == signature(fb_info)
Extending GitHub Issues