So, it turns out that any facebook page, when loaded in an iframe, will explode. That is, it’ll display the facebook logo and nothing else. It’d say it’s an anti-clickjacking measure, if I had to guess.
The problem is that this applies to /any/ iframe, including facebook’s own. This presents a problem when trying to load facebook’s authorization page in a facebook iframe-based app. This was a problem as of Jan 1, 2011, but facebook changes things so fast that it may be resolved by now. If not, here’s how to fix it.
Of course, as stated, step 2 blows up.
Luckily, this all looks like “How it should work” to the user if the redirects are fast.
In app/controllers/oauth_controller:
def start
require 'uri'
escaped_callback_url = URI.escape(FB_CONFIG['callback_url'], Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
signed_request_data = decode_signed_request(FB_CONFIG['app_secret'],params[:signed_request])
if(signed_request_data)
if(signed_request_data["user_id"] != nil)
# User has already or just accepted the app
session['auth_token'] = signed_request_data['oauth_token']
redirect_to "wherever you want to go after authentication"
else
# User hasn't accepted our app
@redirect_url = "https://graph.facebook.com/oauth/authorize?client_id=#{FB_CONFIG['app_id']}&redirect_uri=#{escaped_callback_url}&display=page&scope=publish_stream,user_photo_video_tags&type=user_agent&display=page"
end
else
#We're not in a facebook iframe
redirect_to FB_CONFIG['app_url']
end
end
private
require 'base64'
def base64_url_decode(str)
str += '=' * (4 - str.length.modulo(4))
Base64.decode64(str.gsub("-", "+").gsub("_", "/"))
end
require 'hmac-sha2'
# used to validate signed requests from facebook http://developers.facebook.com/docs/authentication/canvas
def decode_signed_request(facebook_secret, signed_request)
if(signed_request==nil)
return nil
end
if(facebook_secret==nil)
raise "Facebook secret not set."
end
encoded_sig, encoded_data = signed_request.split(".")
decoded_data = base64_url_decode(encoded_data)
decoded_sig = base64_url_decode(encoded_sig)
logger.info "Decoded data = " + decoded_data.inspect
logger.info "Json parsed decoded data = " + ActiveSupport::JSON.decode(decoded_data).inspect
expected_sig = HMAC::SHA256.digest(facebook_secret, encoded_data)
return decoded_sig==expected_sig ? ActiveSupport::JSON.decode(decoded_data) : nil
end
In app/views/oauth:
<% if @redirect_url %>
<script type='text/javascript'>top.location.href = '<%= raw @redirect_url %>';</script>
<% end %>
In config/initializers/load_config:
FB_CONFIG = YAML.load_file("#{RAILS_ROOT}/config/facebook.yml")[RAILS_ENV]
In config/facebook.yml:
production:
app_id: '1234your_app_id'
api_key: '1234your_app_key'
app_secret: '1234your_app_secret'
callback_url: "myserver.com/fb_content/start"
app_url: http://apps.facebook.com/myapp
In Gemfile:
gem 'json'
gem 'ruby-hmac'