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)

Related Articles:

This entry was posted in facebook, ruby. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://profile.yahoo.com/2A2IMY4RXZHYVSSHRE6RCJP64I Shogo

    cookie = request.cookies["fbs_#{@fb_app_id}"]
    on the sinatra server side, my cookie is nil. I am logged in with the javascript sdk with status and cookie set to true for FB.init
    i even hardcoded fbs_123456 #thats not my real app id of course.
    I found your article because 3rd party tools like fb_graph are not working with the javascript sdk (or I can’t get it to work).

    • Anonymous

      This article is now out of date. I discovered the same problem recently. The cookie now comes through as “fbsr_#{@fb_app_id:disqus }”, and I think it is intended to be used in a different way. See some discussion here: http://stackoverflow.com/questions/7206204/new-js-sdk-with-oauth-2-0-saving-subdomain-in-fbsr-cookie

      I will update this article with a warning.

      • http://profile.yahoo.com/2A2IMY4RXZHYVSSHRE6RCJP64I Shogo

        Thanks for the warning Joshua.  Are you planning to move to one of the various 3rd party ruby sdks listed on:  https://developers.facebook.com/tools/third-party-sdks/#ruby

        I’m not sure if any of them satisfy:
         1. Sinatra
         2. Integration with the Facebook SDK for Javascript

        • Anonymous

          I did not update to use a 3rd party SDK. I only needed to make a few changes to my code:

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

          fb_info = JSON.parse(urldecode64(cookie.split(‘.’,2)[1]))

          def urldecode64(str)
          encoded_str = str.tr(‘-_’, ‘+/’)
          encoded_str += ‘=’ while !(encoded_str.size % 4).zero?
          Base64.decode64(encoded_str)
          end

          def valid_cookie?
          return false unless cookie
          return false if fb_info['algorithm'].to_s.upcase != ‘HMAC-SHA256′
          encoded_sig, payload = cookie.split(‘.’, 2)
          sig = urldecode64(encoded_sig)
          expected_sig = OpenSSL::HMAC.digest(‘sha256′, settings.fb_app_secret, payload)
          expected_sig == sig
          end

          • http://profile.yahoo.com/2A2IMY4RXZHYVSSHRE6RCJP64I Shogo

            Thanks Joshua! Its working great! 

      • http://profile.yahoo.com/2A2IMY4RXZHYVSSHRE6RCJP64I Shogo

        for the mean time, Andrew from the #facebook chatroom on irc.freenode.net advised me to look at the PHP opensource code to see how they do it.  This code looks like a good place to start:  https://github.com/facebook/facebook-php-sdk/blob/master/src/base_facebook.php#L987