Python: Convert your FBML Facebook app to IFRAME

Recently, users started reporting that our Facebook app was returning a blank screen. Tracking it down, it turned out that it was specifically when the user was in the new "Use Facebook as a Page" mode. Some other developers have reported a similar issue. Given that FBML apps were recently deprecated, I figured it was time to migration to IFRAMEs.

With our recent launch of Requests and the support for iframe on Pages Tabs, we are now ready to move forward with our previously announced plans to deprecate FBML and FBJS as a primary technology for building apps on Facebook. On March 11, 2011, you will no longer be able to create new FBML apps and Pages will no longer be able to add the Static FBML app. While all existing apps on Pages using FBML or the Static FBML app will continue to work, we strongly recommend that these apps transition to iframes as soon as possible. - Facebook Developers Blog

The first big different is the authentication model. Instead of passing the facebook page id directly, they are now passing a signed_request field. I was able to track down some open source code, and then modify it myself to only optionally take a secret, and remain backwards compatible with the FBML fb_sig_page_id parameter in Django.

import base64
import hashlib
import hmac
import simplejson as json

# from http://sunilarora.org/parsing-signedrequest-parameter-in-python-bas

def base64_url_decode(inp):
    padding_factor = (4 - len(inp) % 4) % 4
    inp += "="*padding_factor
    return base64.b64decode(unicode(inp).translate(dict(zip(map(ord, u'-_'), u'+/'))))

def parse_signed_request(signed_request, secret=None):

    l = signed_request.split('.', 2)
    encoded_sig = l[0]
    payload = l[1]

    sig = base64_url_decode(encoded_sig)
    data = json.loads(base64_url_decode(payload))

    if data.get('algorithm').upper() != 'HMAC-SHA256':
        print "Facebook: Unknown algorithm"
        return None
    else:
        if secret:
            expected_sig = hmac.new(secret, msg=payload, digestmod=hashlib.sha256).digest()

    if secret and sig != expected_sig:
        return None

    return data

def get_facebook_page_id(request):
    facebook_page_id = request.REQUEST.get("fb_sig_page_id")
    if not facebook_page_id:
        signed_request = request.REQUEST.get("signed_request")
        if signed_request and "." in signed_request:
            data = parse_signed_request(signed_request)
            #{'user_id': 'XXX', 'algorithm': 'HMAC-SHA256', 'expires': 0,
            # 'oauth_token': 'XXX',
            # 'user': {'locale': 'en_US', 'country': 'us', 'age': {'min': 21}}, 'issued_at': 1301682530, 'page':
            # {'admin': True, 'liked': False, 'id': 'XXX'}}
            page = data.get("page")
            if page:
                facebook_page_id = page.get("id")

    return facebook_page_id

The only change to my existing view code was to call get_facebook_page_id(request) instead of just request.REQUEST.get("fb_sig_page_id").

The second big change was that you must style your apps yourself if you want it to look like the rest of Facebook in terms of fonts and colors. FBML apps were pretty locked down, which sucked in a lot of ways, but at least it automatically looked like the rest of Facebook. In any case, not such a big deal:

/* put a facebook-app class on your BODY element, or a box div around all your content */
.facebook-app {
    font-size: 11px;
    color: #333;
    font-family: 'lucida grande', tahoma, verdana, arial;
}
.facebook-app a { color: #3B5998; text-decoration: none; }

I didn't have to change my HTML at all; it still omits the html/body tags just like FBML apps are required to do, but at least it's backwards compatible. In fact, I can switch my app between FBML and IFRAME modes and not see much difference.



I'm currently working at NerdWallet, a startup in San Francisco trying to bring clarity to all of life's financial decisions. We're hiring like crazy. Hit me up on Twitter, I would love to talk.

Follow @chase_seibert on Twitter