Facebook Graph API with FBML Canvas Apps

Facebook is now sending a new parameter to canvas applications, signed_request.
This parameter is the concatenation of a JSON object and a signature, both Base64 encoded.
The JSON object contains 4 other parameters:

user_id    ID of the logged in user
oauth_token    The users OAuth access token
expires    When the token expires
profile_id    ID of the profile when rendered in tab

The signature is a HMAC-SHA265 of the JSON and is needed to verify that the request originates from Facebook.

With this new parameter it’s finally possible to use the Graph API in an affordably way with FBML apps.
Just store the access token in your users session and pass it along with your Graph API requests.

Parsing signed_request

I implemented support for the signed_request to buddybrand’s Facebook library for Rails.
The code I’m using for verifying the signature and adding the JSON params to the Rails params via Rack middleware looks like this (extract):

    class ParamsParser
      def initialize(app, &condition)
        @app = app
        @condition = condition
      end

      def call(env)
        request = Rack::Request.new(env)

        signed_request = request.params["signed_request"]
        signature, signed_params = signed_request.split('.')

        # Verify signature
        unless signed_request_is_valid?(Buddy.current_config['secret'], signature, signed_params)
          return Rack::Response.new(["Invalid Facebook signature"], 400).finish
        end

        # Parse JSON
        signed_params = Yajl::Parser.new.parse(base64_url_decode(signed_params))

        # Add JSON parameters to Rails params
        signed_params.each do |k,v|
          request.params[k] = v
        end
        
        @app.call(env)
      end

      private
      def signed_request_is_valid?(secret, signature, params)
        signature = base64_url_decode(signature)
        expected_signature = OpenSSL::HMAC.digest('SHA256', secret, params.tr("-_", "+/"))
        return signature == expected_signature
      end

      # Stolen from mini_fb.
      # Ruby's implementation of base64 decoding reads the string in multiples of 6 and ignores any extra bytes.
      # Since facebook does not take this into account, this function fills any string with white spaces up to
      # the point where it becomes divisible by 6, then it replaces '-' with '+' and '_' with '/' (URL-safe decoding),
      # and decodes the result.
      def base64_url_decode(str)
        str = str + "=" * (6 - str.size % 6) unless str.size % 6 == 0
        return Base64.decode64(str.tr("-_", "+/"))
      end
    end

 

Using the Graph API

Using the Graph API is just as simple as this:

curl https://graph.facebook.com/me?access_token=YOUR_TOKEN

This will respond with a JSON object containing your basic user info.

Talking to the Graph API with Ruby / HTTParty is not much more complicated then using curl:

  HTTParty.get("https://graph.facebook.com/me", :access_token => "YOUR TOKEN")

 

Old params deprecation

In the future Facebook will deprecate all other parameters than signed_request, but there’s no exact timing right now.

There’s some official documentation on how to migrate from the old params to signed_request available at developers.facebook.com.

UPDATE 2010-07-26

Facebook silently stopped sending the signed_request parameter.

You can enable them again by activating the “OAuth 2.0 for Canvas (beta)” migration in you app settings on Facebook, but this disables all other parameters. So make sure to migrate your app to only rely on signed_request.

Presenting Textseed

Textseed is a project by one of my friends which I want to present to you.

What is Textseed?
To quote the landing page, Textseed is a more organic way to write your documents! Let your documents and thoughts grow through other’s people’s contributions, just as flowers grow when receiving sun and water.

What does it do?
Textseed gives you the opportunity to write texts together with your friends or colleagues and to keep track on who wrote what.
If your team has to complete a report or argues about the discussion items for a presentation, it can happen on Textseed in an organic way.

Textseed is meanwhile stable and the text editing functions are complete.
Up next it will be possible to invite people to your projects and to export your documents as PDF via a LaTeX bridge.

Take a look at Textseed at textseed.com.

Pic(k)host CLI client

Blog post at blog.pickhost.eu

Bitly API

I created a small and simple library to access the bitly API.

Usage is simple:

>> api = BitlyApi::Bitly.new(:login => BITLY_LOGIN, :api_key => BITLY_API_KEY)
=> #<BitlyApi::Bitly:0x7f93177c87e0 @api_version="2.0.1", @api_key=BITLY_API_KEY, @login=BITLY_LOGIN>
>> api.shorten "http://pickhost.eu"
=> {"userHash"=>"2wzb4d", "hash"=>"zNgN6", "shortKeywordUrl"=>"", "shortUrl"=>"http://bit.ly/2wzb4d"}
>> api.expand "2wzb4d"
=> {"longUrl"=>"http://pickhost.eu/"}
>> api.stats "2wzb4d"
=> {"userClicks"=>1, "referrers"=>{""=>{"direct"=>1}}, "userHash"=>"2wzb4d", "hash"=>"zNgN6", "clicks"=>1, "userReferrers"=>{""=>{"direct"=>1}}}
>> api.info "2wzb4d"
=> {"thumbnail"=>{"small"=>"http://s.bit.ly/bitly/zNgN6/thumbnail_small.png", "medium"=>"http://s.bit.ly/bitly/zNgN6/thumbnail_medium.png", "large"=>"http://s.bit.ly/bitly/zNgN6/thumbnail_large.png"}, "id3"=>{}, "userHash"=>"2wzb4d", "hash"=>"zNgN6", "mirrorUrl"=>"", "htmlMetaDescription"=>"An image hosting service with simplicity and speed in mind.", "version"=>1.0, "htmlTitle"=>"Pic(k)host :: Upload your Images!", "globalHash"=>"zNgN6", "calaisId"=>"", "calais"=>{}, "users"=>["pickhost"], "surbl"=>0, "longUrl"=>"http://pickhost.eu/", "htmlMetaKeywords"=>["Bilder hochladen", "Bilder upload", "Foto upload", "Bilderhoster", "Imagehoster", "Foto", "Bild", "Photo", "Image", "Pic", "Hoster"], "contentType"=>"text/html; charset=utf-8", "keyword"=>"", "exif"=>{}, "contentLength"=>"", "shortenedByUser"=>"pickhost", "metacarta"=>[], "keywords"=>[], "calaisResolutions"=>{}}

The project is located at GitHub.

Russian Technology

Last night I switched the Webserver/Load-Balancer from Lighttpd to Nginx.
It took about 5 hours to port 1065 lines of Lighttpd config to Nginx, but it was definitely the right decision.

The only disadvantage of Nginx is that it doesn’t support CGI ( which could be a big security advantage ).
I solved this for now with a Lighty listening on localhost and proxying all requests to CGI applications through it.

Concerning the performance I’m really impressed of Nginx.
It serves static files light years faster than Lighttpd, and with the fair load balancer module it kicks ass at load balancing.