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