Search code examples
rubyherokusinatrafbconnect

How to make a Sinatra App on Heroku work with fb_graph?


I created a sinatra based facebook app on Heroku by initiating it with the feature provided by Facebook (Get Cloud App or something like that). Everything worked fine, but when I tried to use the Scores from Facebook, I realized that the Facebook API used by Heroku (which is Mogli) does not support Scores. So I tried to rewrite the code to use fb_graph. But since that created more confusion than everything I started with an empty file. But I just can't make it work.

So basically I would like a simple example for a Sinatra based app on heroku which uses fb_graph. The samples should only contain authentication, since everything else seems pretty straight forward and has a lot of documentation. Just the initial authentication just doesn't work.

I would like to use the default oauth2 approach with the callback, since it just feels more natural to me, but I'm open for everything. I know there is a Rails example, but I just can't get my head around it and since my app will be very simple Rails seems a bit overpowered.

It would be great if anyone could give me just the few lines necessary for what I would like to do!


Solution

  • I've found this to be a bit of a mess. Old tutorials and code just don't make sense anymore.

    You can use the Heroku + Facebook OmniAuth example to do an external website (not a canvas app) that does server-side Facebook (and many others) authentication.

    When you do that, OmniAuth will provide you with the token you need you need to pass to fb_graph. In the provided example you could add another url:

    get '/me' do
      me = FbGraph::User.me(session['fb_token']).fetch
      "Hello " + me.name
    end
    

    For canvas apps, I added the following code to 'rack-facebook-request.rb' to my repo based on this gist

    require 'base64'
    require 'openssl'
    require 'json'
    
    # This is inspired by [rack-facebook-signed-request](https://github.com/gamesthatgive/rack-facebook-signed-request)
    #
    # Usage
    #
    #     use Rack::FBSignedRequest, :secret => 'SECRET'
    #
    class Rack::FBSignedRequest
      def initialize(app, options)
        @app = app
        @options = options
      end
    
      def call(env)
        @request = Rack::Request.new(env)
        if @request.POST['signed_request']
          if facebook_params = parse_signed_request(@request.params['signed_request'])
            @request.params['facebook_params'] = facebook_params
            env['rack.request.query_hash'] = @request.params
            env['REQUEST_METHOD'] = 'GET'
            puts 'Valid signed request. Changed REQUEST_METHOD to GET.'
    
            if facebook_params['user_id']
              env['fb_user_id'] = facebook_params['user_id']
              env['fb_access_token'] = facebook_params['oauth_token']
              puts 'Request has been authorized.'
            else
              puts 'Request is not authorized.'
            end
    
          else
            puts 'Not a valid signed request'
          end
        else
           puts 'Not a signed_request'
        end
    
        @app.call(env)
      end
    
      private
    
      # The following code from omniauth
    
      def parse_signed_request(value)
        signature, encoded_payload = value.split('.')
    
        decoded_hex_signature = base64_decode_url(signature)
        decoded_payload = JSON(base64_decode_url(encoded_payload))
    
        unless decoded_payload['algorithm'] == 'HMAC-SHA256'
          raise NotImplementedError, "unkown algorithm: #{decoded_payload['algorithm']}"
        end
    
        if valid_signature?(@options[:secret], decoded_hex_signature, encoded_payload)
          decoded_payload
        end
      end
    
      def valid_signature?(secret, signature, payload, algorithm = OpenSSL::Digest::SHA256.new)
        OpenSSL::HMAC.digest(algorithm, secret, payload) == signature
      end
    
      def base64_decode_url(value)
        value += '=' * (4 - value.size.modulo(4))
        Base64.decode64(value.tr('-_', '+/'))
      end
    end
    

    This will give you the current fb_user_id and fb_access_token if your app is authorised. Remember that you have to use javascript or a link in the iFrame to request app authorisation in this case.