Spending my days programming virtual worlds for The Electric Sheep Company. Spending nights and weekends on BlitzPick for Super + Fun. I also enjoy quilting, reading, and singing in the shower.

Test controllers with Facebooker Mock Sessions

To avoid login redirects when testing controllers that use Facebooker’s ensure_authenticated_to_facebook before_filter, use a mock session:

    get(:index, {}, {
      :facebook_session => Facebooker::MockSession.create})
    assert_response :success

Friday, February 12th, 2010

Overriding link_to in a Ruby on Rails/Facebooker iframe app

I’m working on a Facebook app using Ruby on Rails and Facebooker. The app works as a Connect site or in an iframe app. Navigation in iframe apps is trickier than in fbml apps because links with the default target change only the iframe address, not the page address (shown in the address bar). Furthermore, Facebook only adds the fb_sig_ params when the Facebook app page is loaded. When a user clicks a link that changes only the iframe url, the fb_sig_ params aren’t passed along automatically.

After reading this blog entry and its comments, I decided to get around the issue by overriding link_to to automatically set the target to _top for links in the iframe app. I’m using the absolute url for those links, which feels wrong but I’m not sure how else to make sure it sets the address to the Facebook page instead of the canvas url.

link_to in ApplicationHelper:

def link_to(name, options = {}, html_options = {})
  if params[:fb_sig_in_iframe]
    rel = url_for options
    url = "http://apps.facebook.com/" + FACEBOOKER['canvas_page_name'] + rel
    html_options[:href] = url
    html_options[:target] = "_top"
    super(name, {}, html_options)
  else 
    super(name, options, html_options)
  end
end

Maybe later I’ll handle passing the fb_sig_ params instead of changing the page address. Setting top.location.hash would still allow for direct links (with additional work) and would load faster.

Monday, November 30th, 2009

Facebook Authentication with the ActionScript API

I created a little demo app to help me understand Facebook authentication from flash.

Goals

  1. Use only javascript and actionscript, no php or other Facebook client libraries
  2. The secret key is not hardcoded
  3. The same codebase can be deployed on non-Facebook site or in on Facebook app canvas
  4. The app can be used by both authenticated and unauthenticated users
  5. Users can authenticate with Facebook after loading app without reloading the app

The App

If you are logged into facebook and have authorized the app, the demo pulls in your profile picture and user name and displays them in a panel. If you are not logged into facebook or have not yet authorized the app, it shows a login button which will show a popup (from a connect site) or redirect to the login page.

Facebook Connect site embedded as an iframe:

The same app is available as a Facebook iframe canvas page here.

Goal 1: Use only javascript and actionscript, no php or other server-side facebook client libraries

I wanted to use as few languages as possible and maximize what is done from flash. It worked well as described below.

Goal 2: Secret key is not hardcoded

From what I can tell, there’s no way to start a web session solely from the actionscript api (at least not without hardcoding the secret key, which we want to avoid). Instead, the session information can be passed to the swf. In the demo, the actionscript session is created as:

protected function connect(parameters:Object) : void {
    if (parameters.fb_sig_api_key && parameters.fb_sig_ss 
        && parameters.fb_sig_session_key) {
        session = new WebSession(parameters.fb_sig_api_key,
                     parameters.fb_sig_ss, parameters.fb_sig_session_key);
        session.facebook_internal::_uid = parameters.fb_sig_user;
            
        //Create our facebook instance
        facebook = new Facebook();
        session.addEventListener(FacebookEvent.CONNECT, onSessionConnect);
        facebook.startSession(session);
        session.verifySession();
    }
    ...
}

Details on acquiring the session variables are covered under the next goal.

Goal 3: The same codebase can be deployed on a non-Facebook site or on a Facebook app canvas

When loading an app via a Facebook iframe canvas, a number of parameters are appended to the iframe url’s query string, listed here. (The fb_sig_ parameters can also be included using fb:swf on an fbml canvas). In this app, I’ve used javascript to parse the query string into an object and pass to the swf as flashvars on load if the fb_sig_in_iframe=1 parameter is available. The same could be accomplished with a server-side language like php, but I wanted to keep the mix of languages to a minimum.

When loading the app from a Facebook Connect site, the session information is acquired through the javascript Facebook client first, then passed to the swf which sets up its session in the same way as above. In this case, the javascript translates the session variables to the same names used in the iframe query string.

var sessionData = FB.Facebook.apiClient.get_session();
var fbdata = {};
if (sessionData) {
    fbdata.fb_sig_session_key = sessionData.session_key;
    fbdata.fb_sig_ss = sessionData.secret;
    fbdata.fb_sig_user = sessionData.uid;
    fbdata.fb_sig = sessionData.sig;
    fbdata.fb_sig_api_key = api_key;
} 

Goal 4: The app can be used by both authenticated and unauthenticated users

This one is pretty easy: simply do not require a session. Your app will have limited data available, but users who haven’t authorized the app or who don’t have Facebook accounts can still use it if you accommodate them.

Goal 5: Users can authenticate with Facebook after loading app without reloading the app

I had mixed results here. I was unable to get this to work in a canvas page since calls to window.open and FB.Connect.requireSession are ignored in the iframe canvas. Instead, I redirect to facebook.com/login.php if the user needs to log in or authorize the app and then they are returned to the canvas page. This makes some sense since the whole Facebook page needs to refresh if you are logging in.

Logging in from a facebook iframe canvas page:
self.parent.location =  'http://www.facebook.com/login.php?api_key=' + api_key
                         + '&extern=1&fbconnect=1&v=1.0'
                         + '&next=' + escape(CANVAS_PAGE_URL)
                         + '&cancel_url=' + escape(CANVAS_PAGE_URL); 

You can log in without a page refresh in a Facebook Connect site. When the user clicks the “Connect with Facebook” button in the swf, it makes an external interface call to a javascript function. When not in an iframe canvas page, the javascript calls FB.Connect.requireSession. By passing true for the isUserActionHint parameter, the function opens a popup window where the user can login or authorize the app and then closes itself when done.

Logging in via Facebook Connect
FB.ensureInit(function() {
    FB.Connect.requireSession(null, true);  
});

The app page is notified of the status change through the cross domain receiver channel. I initialized the javascript client with a callback for when the user’s logged in status changes. In the callback, the updated session parameters are passed to the swf through another external inteface call.

Initializing the javascript Facebook client:
FB_RequireFeatures(["Api", "Connect"], function() {
    FB.Facebook.init(api_key, 'xd_receiver.html', 
            {   "ifUserConnected": onConnectStatus, 
               "ifUserNotConnected": onConnectStatus
            });   
    });

The Facebook actionscript api docs for the WebSession class recommend using FacebookSessionUtil for managing sessions instead of using WebSession directly. However, FacebookSessionUtil looks for some values only in loaderInfo, which isn’t compatible for updating the session info after the app loads. The demo does use a WebSession instead of FacebookSessionUtil, but it still gets all the necessary information passed in via javascript and doesn’t have hardcoded api or secret keys. The demo doesn’t look for a stored session in a shared object, but it could be added to mimic FacebookSessionUtil closely that way.

Links

View demo using Facebook Connect.

View demo in a Facebook iframe canvas

View and download the source

Reference

Monday, October 26th, 2009