0
votes

I'm creating an iframe canvas app for facebook using the oauth2 branch of the Devise gem. Facebook connect works pretty easily, but I don't understand enough about the gem, how it uses omniauth, warden, etc - to get authentication working with facebook canvas.

Does anyone have an example of how to do this?

Clarification: This is with the "beta" oauth option enabled in the facebook application settings and canvas URL set to /users/oauth/facebook/callback/

I'm also trying to figure out how to get extended permissions.

2

2 Answers

0
votes

I was able to get this working with an ugly hack. It would be great if someone who has some more knowledge of the inner workings of these modules would suggest the proper way to do this - there are many ways I just don't know enough to provide a clean implementation. I monkey patched the oauth helper to pull the oauth token from the signed request. I still haven't completed the implementation so maybe I will have more trouble. This could also leverage the user_id (facebook uid) which is included in the signed request instead of making a call to the graph api.

I've updated this to include a redirect if the user isn't yet authorized as documented here:

http://developers.facebook.com/docs/authentication/

"If the JSON object doesn't contain the user_id parameter, you should redirect the user to the application authorization screen as per the normal OAuth 2.0 Web Application flow."

However, this displays a facebook logo with a link to that URL instead, and then pops the user out of the iframe - some sort of iframe defeat by facebook that I think shouldn't be there.

module Devise
  module Oauth
    module InternalHelpers
      def callback_action
        access_token = nil
        if params[:signed_request]
          signed_data = FacebookSignedRequest.new(params[:signed_request]).valid?
          if signed_data['oauth_token']
            access_token = oauth_config.access_token_by_token(signed_data['oauth_token'])
          else
            return redirect_to oauth_config.authorize_url(:redirect_uri => oauth_redirect_uri)
          end
        elsif params[:code]
          access_token = oauth_config.access_token_by_code(params[:code], oauth_redirect_uri)
        else
          return redirect_to oauth_config.authorize_url(:redirect_uri => oauth_redirect_uri)
        end

        self.resource = resource_class.send(oauth_model_callback, access_token, signed_in_resource) if access_token

        if resource && resource.persisted? && resource.errors.empty?
          set_oauth_flash_message :notice, :success
          sign_in_and_redirect resource_name, resource, :event => :authentication
        else
          session[oauth_session_key] = access_token.token
          clean_up_passwords(resource)
          render_for_oauth
        end
      end

      def valid_oauth_callback?
        return !!params[:signed_request]
      end
    end
  end
end

### For parsing the signed request

require 'base64'
require 'json'
require 'openssl'

# the code is copied from
# http://forum.developers.facebook.net/viewtopic.php?pid=250261
class FacebookSignedRequest

  attr_reader :signed_request

  def initialize(signed_request)
    @signed_request = signed_request
  end

  def base64_url_decode str
    encoded_str = str.gsub('-','+').gsub('_','/')
    encoded_str += '=' while !(encoded_str.size % 4).zero?
    Base64.decode64(encoded_str)
  end

  def valid?
    secret = "facebook-secret"

    #decode data
    encoded_sig, payload = signed_request.split('.')
    sig = base64_url_decode(encoded_sig).unpack("H*")[0]
    data = JSON.parse base64_url_decode(payload)

    if data['algorithm'].to_s.upcase != 'HMAC-SHA256'
    # Rails.logger.error 'Unknown algorithm. Expected HMAC-SHA256'
      return false
    end

    #check sig
    expected_sig = OpenSSL::HMAC.hexdigest('sha256', secret, payload)
    if expected_sig != sig
    # Rails.logger.error 'Bad Signed JSON signature!'
      return false
    end

    data
  end
end
0
votes

Probably too late now, but you need to add

skip_before_filter :verify_authenticity_token, :only => [:action_name]

to the top of your controller.