Facebook Developers
DocsToolsSupportNewsApps
Log In
  • Social Plugins
  • Facebook Login
  • Open Graph
  • Facebook APIs
  • Games
  • Media
  • Payments
  • App Center
  • Promote Your App
  • iOS
  • Android
  • Web
  • Technology Partners
  • Samples
    • Sample Canvas App

Sample Canvas App

Documentation › Samples › Sample Canvas App

Overview

Run with Friends is a sample Facebook Canvas Application written in Python running on Google App Engine. The application is designed to run within an iframe on a Facebook Canvas page.

Canvas Screenshot

Facebook provides a large array of integration points for applications and this sample application makes use of only a few of those capabilities. Some of the things that are covered here are:

  • Web based Authentication, specifically using the JavaScript SDK and the signed_request.
  • Making use of a Social Plugin, notably the Login with Faces plugin.
  • Showing Facebook Dialogs to allow the user to post content back to Facebook.
  • Making Graph API calls using OAuth 2.0.
  • Making use of Real-time Updates in order to keep the cached data up-to-date.

You can try the sample at http://apps.facebook.com/runwithfriends/


Requirements

In order to run the sample, you need the following:

  • Python
  • Google App Engine
  • A public URL for your application (in order to use the Real-time API)

Getting Started

  1. Create a Facebook Application

  2. In the Web Site section:

    • Configure the Site URL, and point it to your Web Server. If you're developing locally, you can use http://localhost:8080/
  3. In the Facebook Integration section:

    • Choose a Canvas Page name, which is your application's URL on facebook.com.
    • Configure the Canvas URL, and point it to your webserver. If you're developing locally, you can use http://localhost:8080/. This will get used as the iframe src.
    • Also switch iframe Size to Auto Resize, since we'll be using the JavaScript SDK to resize our iframe.
  4. Download the sample:

    mkdir runwithfriends
    cd runwithfriends
    curl -L http://github.com/fbsamples/web-runwithfriends/tarball/master |
      tar xzvf - --strip-components=1
    
  5. Configure the local settings for your app. Start by copying conf.py.example to conf.py, then edit the various values in the conf.py:

    cp conf.py.example conf.py
    vim conf.py
    
  6. Add the application to the App Engine Launcher and start it (or start it from the command line):

    dev_appserver.py .
    
  7. Go to localhost:8080


The User Model

One of the main benefits of integrating with Facebook is the ease with which you can use the user's real identity provided by Facebook, and the wealth of existing user data that is made available. For the sample application, we used a few different pieces of data to represent the User:

  • Facebook User ID - the core stable identifier for a user
  • Name
  • Email
  • Profile Picture
  • Friends (set of Facebook User IDs for that are friends with the user)

The first step to getting this information is having the user authorize your application and grant it the necessary permissions. The result of granting these permissions gives you the access_token which enables access to the APIs that will give you the above data.

In the sample application we've opted to represent all this information as part of the User model, and is defined as:

class User(db.Model):
    user_id = db.StringProperty(required=True)
    access_token = db.StringProperty(required=True)
    name = db.StringProperty(required=True)
    picture = db.StringProperty(required=True)
    email = db.StringProperty()
    friends = db.StringListProperty()
    dirty = db.BooleanProperty()

signed_request Parameter

The typical flow for a user when using the application begins with the user landing at some Canvas URL, like http://apps.facebook.com/runwithfriends/. At this point, Facebook will load up its chrome, and render a <iframe> tag to your application. You'll notice there isn't a src specified. Using some JavaScript and the <form> tag, Facebook triggers a POST request to your application. This is done for security reasons, as the sensitive user data won't be sent via the HTTP Referrer header as long it's sent as POST data. Additionally, in order to ensure the data Facebook sends over is not tampered with, it provides a signed JSON data structure, which is signed using the application secret. The parameter is called signed_request, and sample application uses this to load it:

def load_signed_request(self, signed_request):
    """Load the user state from a signed_request value"""
    sig, payload = signed_request.split(u'.', 1)
    sig = self.base64_url_decode(sig)
    data = json.loads(self.base64_url_decode(payload))

    expected_sig = hmac.new(
        self.app_secret, msg=payload, digestmod=hashlib.sha256).digest()

    # allow the signed_request to function for upto 1 day
    if sig == expected_sig and data[u'issued_at'] > (time.time() - 86400):
        self.signed_request = data
        self.user_id = data.get(u'user_id')
        self.access_token = data.get(u'oauth_token')

When Facebook sends the initial POST request, if it detects that the user has already authorized your application, it will include the User ID and the access_token as part of the signed_request parameter. The application uses this information, and sets an internal auth cookie named u. We choose to use the same format as for the cookie value as the signed_request parameter in order to simplify/reuse our authentication code. The signed_request parameter is documented at length in the official documentation.

The sample application uses this to initialize the Facebook instance, and if possible the User instance on every request:

def init_facebook(self):
    """Sets up the request specific Facebook and User instance"""
    facebook = Facebook()
    user = None

    # initial facebook request comes in as a POST with a signed_request
    if u'signed_request' in self.request.POST:
        facebook.load_signed_request(self.request.get('signed_request'))
        self.request.method = u'GET'  # causes loss of request.POST data
        self.set_cookie(
            'u', facebook.user_cookie, datetime.timedelta(minutes=1440))
    elif 'u' in self.request.cookies:
        facebook.load_signed_request(self.request.cookies.get('u'))

    # try to load or create a user object
    if facebook.user_id:
        user = User.get_by_key_name(facebook.user_id)
        if user:
            # update stored access_token
            if facebook.access_token and \
                    facebook.access_token != user.access_token:
                user.access_token = facebook.access_token
                user.put()
            # refresh data if we failed in doing so after a realtime ping
            if user.dirty:
                user.refresh_data()
            # restore stored access_token if necessary
            if not facebook.access_token:
                facebook.access_token = user.access_token

        if not user and facebook.access_token:
            me = facebook.api(u'/me', {u'fields': u'picture,friends'})
            logging.info(u'/me API call response: ' + unicode(me))
            user = User(key_name=facebook.user_id,
                user_id=facebook.user_id,
                access_token=facebook.access_token,
                name=me[u'name'],
                email=me.get(u'email'),  # optional
                picture=me[u'picture'],
                friends=[user[u'id'] for user in me[u'friends'][u'data']])
            user.put()

    self.facebook = facebook
    self.user = user

There are a several important decisions here that ensure a smooth experience for users:

  1. We prefer the signed_request sent in the POST rather than a cookie value. If we don't find signed_request in the POST data, we lookup the cookie value instead.
  2. We set a cookie on every POST that includes a signed_request. This ensures we persist the last well known user state.
  3. We reset the method to GET when we get a POST with the signed_request. Facebook will always send a POST on the initial iframe request for security reasons, and this can be to any end-point in our application, while in reality, this should be treated as a GET. By resetting the method to GET, we make the rest of our application ignorant of this behaviour.
  4. While we use the signed_request format in the cookie, this is not a Facebook requirement. You could use your own session storage without any consequences, and all that is required is that you store the Facebook user_id to identify the logged in user. Just remember to prefer the signed_request in POST if it's there and update your session the same way as the cookie is updated.
  5. We don't simply set the signed_request value we get in the POST as the cookie because it will contain the access_token and may be large. Instead, we simply store the user_id in the cookie, and load the stored access_token from the user object as needed.
  6. If we get a signed_request in the POST, we update the stored access_token with the freshly received one if it isn't the same, always assuming it's the better one of the two.
  7. If we don't find a stored user, and the request included an access_token, we'll create the user object.
  8. The dirty bit is used along with the Real-time API which is described further below.

Login with Faces

Authentication with Facebook involves sending the user to facebook.com and having Facebook prompt them to authorize the application. The simplest way to do so is to redirect the user's browser to facebook.com with some parameters, and have the user come back after they've taken an action. If you want to go the manual route, the documentation for authenticating users in a web application will provide the easiest path. In the sample application we've opted to use the login with faces plugin via the JavaScript SDK to provide a richer experience. The plugin will show a login button, and the faces of the friends who have used the application (if possible). The data is not actually made available to the application yet, the social context is provided by using an iframe to facebook.com. This is done via XFBML, a client side markup language supported by the JavaScript SDK:

<fb:login-button perms="email,offline_access"
                 show-faces="true"></fb:login-button>

The result of which is:

Login with Faces

Notice the perms attribute on the tag -- this lists the extended permissions we'll need the user to grant our application in order for it to function. Upon clicking the Login button, the user will be shown a popup dialog to grant your application the requested permissions:

Permissions Dialog

The sample application requests extended permissions, but doesn't actually need it! Ideally you should ask for no additional permissions when the user initially authorizes your application, and keep the list of things in the above dialog short. You can ask for additional permissions even after the user has already authorized your application for basic information. Requesting minimal permissions will mean a greater initial conversion, giving you the opportunity to provide the user with an experience which makes for a more compelling backdrop when you ask for the additional permissions at a later point in time.


Authentication Events

JavaScript is a heavily event driven environment, and the JavaScript SDK embraces that nature and provides its own events. Notably, there are two useful event's we care about in the authentication context: auth.login and auth.logout. The JavaScript SDK fires these in response to various interactions, including a login action from the <fb:login-button>. These can be subscribed to as:

FB.Event.subscribe('auth.login', function(response) {
  // Reload the entire page. Could also do an Ajax request and dynamically
  // update the already loaded page.
  window.location.reload(true);
});

FB.Event.subscribe('auth.logout', function() {
  // Do something...
});

These events let you do some custom work whenever a relevant change happens. Remember to be careful about when you subscribe to these events. It's usually ideal to invoke FB.Event.subscribe() right after the FB.init() call. For most applications which do the heavy lifting on the server side, you'll typically just want to reload the top frame and have Facebook send you a signed_request with the user's credentials, and let the server render the logged in view. This is what the sample application does:

function handleSessionChange(response) {
  if ((Config.userIdOnServer && !response.session) ||
      Config.userIdOnServer != response.session.uid) {
    top.location = 'http://apps.facebook.com/' + Config.canvasName + '/';
  }
}
FB.Event.subscribe('auth.sessionChange', handleSessionChange);

The sample application also handles in-flight user changes. It does this by comparing the user_id of the logged in user when the server rendered the page (or lack there of) with the user_id based on the auth events in JavaScript (which is doing a live check against facebook.com and is more accurate out of the two).


Cookies in iframes/P3P Header

Some browsers will let iframes set cookies based on the presence of the P3P header. Notably, IE respects this header. Ideally you may want to look up the right value based on the privacy policy adopted by your application, but any value will usually suffice. The sample application sends this for instance:

P3P: CP="HONK"

Graph API

The Graph API provides a RESTful interface to the Facebook API. The API makes a wealth of information available, assuming you've requested the necessary extended permissions. The basic access pattern to use the API is:

https://graph.facebook.com/[PATH]?access_token=[TOKEN]&[PARAMS...]

The Facebook class provides a simple interface to access the Facebook API. This may not be sufficient for more advance use cases (such as file uploads):

def api(self, path, params=None, method=u'GET', domain=u'graph'):
    """Make API calls"""
    if not params:
        params = {}
    params[u'method'] = method
    if u'access_token' not in params and self.access_token:
        params[u'access_token'] = self.access_token
    result = json.loads(urlfetch.fetch(
        url=u'https://' + domain + u'.facebook.com' + path,
        payload=urllib.urlencode(params),
        method=urlfetch.POST,
        headers={
            u'Content-Type': u'application/x-www-form-urlencoded'}).content)
    if isinstance(result, dict) and u'error' in result:
        raise FacebookApiError(result)
    return result

Note, here we're using the method override, that is, specifying the method as a parameter to specify the intended HTTP method as a convenience.

Getting the User data

Once we have a Facebook user_id and access_token, we can make use of that to populate our user object using the API method described above. Here's what the sample application does to fetch the date we care about. Notice we request the additional picture field, as well as the friends data as part of the single API call. Additionally, you'll notice email is not actually required, but will be populated if it's available. We do not need to do the same for name, picture and friends as that is part of the minimal set of information made available when a user authorizes your application.

me = self.facebook.api(u'/me', {u'fields': u'picture,friends'})
logging.info(u'/me API call response: ' + unicode(me))
user = User(key_name=self.facebook.user_id,
    user_id=self.facebook.user_id,
    access_token=self.facebook.access_token,
    name=me[u'name'],
    email=me.get(u'email'),  # optional
    picture=me[u'picture'],
    friends=[user[u'id'] for user in me[u'friends'][u'data']])
user.put()

The Graph API provides a simple interface to a wealth of facebook data. The sample application uses only some basic user information, including the user's friends list, but after requesting the right set of permissions, you can access the user's photos, events, groups, checkins and a lot more.


Dialogs

Once the user successfully posts a run, we want to provide them with the option to post a story to Facebook. Again, you could manually popup a window to the dialog, we can provide a richer user experience by making use of the JavaScript SDK and making use of FB.ui().

Here's what the sample application uses to publish the story:

function publishRun(title) {
  FB.ui({
    method: 'stream.publish',
    attachment: {
      name: title,
      caption: "I'm running!",
      media: [{
        type: 'image',
        href: 'http://runwithfriends.appspot.com/',
        src: 'http://runwithfriends.appspot.com/splash.jpg'
      }]
    },
    action_links: [{
      text: 'Join the Run',
      href: 'http://runwithfriends.appspot.com/'
    }],
    user_message_prompt: 'Tell your friends about the run:'
  });
}

After the user adds a Run, we show the user a confirmation message with a link that triggers this function, and results in an experience like such:

Publish Dialog

The stream publish dialog provides an easy, intentional user action that allows your application to post content back to Facebook. The API allows you to specify various attributes that control the display and the behaviour of the posts. As this process already involves an explicit user interaction, you are able to make use of this without permissions of any sort. In fact, you can show the popup version of the stream publish dialog even before the user has authorized your application.


Real-time Updates

Our approach to use the basic user data is to make an API call to Facebook and cache the data in our application's local data store. We also subscribe to the Real-time API which notifies us when the data we care about changes, and App Engine provided Task Queues to manage the updates that should be triggered based on the pings. The two together allow us to provide a vastly improved user experience as we no longer need to make expensive HTTP calls as the user is using the application, but yet always have the most up-to-date information about the user including their name, email, profile picture and list of friends as long as the user is actively using our application.

We use a two fold approach here. We store the access_token we get when the user visits the application, and will try to use that to make an API call to refresh the user data in a background task when we are notified of changes. If this API call fails, we'll flip the dirty bit on the user instance. This will mean we'll use some stale data for the user, until they visit the application the next time around at which point we'll have a fresh access_token, and we'll notice the dirty bit and trigger a refresh of the data. We use the two fold system despite the fact that we ask for the offline_access permission (which in theory means indefinite access). It is always a good idea to handle the case where the stored access_token is no longer valid, which can happen for various reasons (user explicitly revoked access, token expired and a few others).

The process of using the Real-time API begins with subscribing to object families. Our application subscribes to the user object, and specifically the ['email', 'name', 'picture', 'friends'] fields.

For our sample application, we've setup an internal end-point that enables us to trigger the subscription setup flow. Simply visit http://localhost:8080/realtime?setup=1. If you get an error message, ensure you've added your own Facebook User ID to the ADMIN_USER_IDS in conf.py, and that you're logged into the application. This is our poor mans admin system.

The sample application uses this to setup the subscription:

def setup_subscription(self):
    path = u'/' + conf.FACEBOOK_APP_ID + u'/subscriptions'
    params = {
        u'access_token': conf.FACEBOOK_APP_ID + u'|' +
                         conf.FACEBOOK_APP_SECRET,
        u'object': u'user',
        u'fields': u'name,email,picture,friends',
        u'callback_url': conf.EXTERNAL_HREF + u'realtime',
        u'verify_token': conf.FACEBOOK_REALTIME_VERIFY_TOKEN,
    }
    response = self.facebook.api(path, params, u'POST')
    logging.info(u'Real-time setup API call response: ' + unicode(response))

Here we're making an application secret enabled call, as can be seen by the fact that we're using application ID and the application secret as the access_token. We are informing Facebook's Real-time API service about our configuration, including the objects and fields we want to get change notifications about, as well as the endpoint to hit. Be careful, you'll need to ensure the EXTERNAL_HREF has been configured correctly, and is a end-point that can be reached from anywhere on the internet. This means you cannot use something like http://localhost:8080/ here, because that is not an end-point Facebook's servers can reach. Once you've triggered the setup, you'll receive a background ping from Facebook's servers to confirm the subscription after which you'll start receiving change notifications.

Here's the top level entry point for our Real-time API change notifications:

def post(self):
    body = self.request.body
    logging.info(u'Real-time ping: ' + body)
    if self.request.headers[u'X-Hub-Signature'] != (u'sha1=' + hmac.new(
        self.facebook.app_secret,
        msg=body,
        digestmod=hashlib.sha1).hexdigest()):
        logging.error(
            u'Real-time signature check failed: ' + unicode(self.request))
        return
    data = json.loads(body)

    if data[u'object'] == u'user':
        for entry in data[u'entry']:
            taskqueue.add(url=u'/task/refresh-user/' + entry[u'id'])
            logging.info('Added task to queue to refresh user data.')
    else:
        logging.warn(u'Unhandled Real-time ping: ' + body)

First we ensure that the signature of the ping matches can be verified based on our application secret. As we're only subscribing to user object changes, we have only implemented the same, and log a warning if another type of notification is received.

The other interesting aspect here is that we may get batches of changes, as represented by the entry property in the notification, which is an array of objects. The Real-time API needs confirmation when you've handled the ping. Success is indicated by simply returning a 200 response within 10 seconds. This coupled with the large batch sizes means we cannot do the work of refreshing the user data in the same request that receives the Real-time ping as we would not have enough time. Additionally, App Engine itself restricts each request to 30 seconds or so, which would be another limitation. So we simply queue up independent tasks for each user_id mentioned in the ping, and do the work of refreshing the data in the background task. Ideally, we'll be successful in refreshing the data, but if we're not, we mark the user instance as dirty to trigger a refresh the next time the user visits our application.

class RefreshUserHandler(BaseHandler):
    """Refresh user data using if possible."""
    def post(self, user_id):
        logging.info('Refreshing user data for ' + user_id)
        user = User.get_by_key_name(user_id)
        if not user:
            return
        try:
            user.refresh_data()
        except FacebookApiError:
            user.dirty = True
            user.put()

This process keeps our internal copy of the user data up-to-date, and essentially enables us to limit ourselves to invoking the Facebook Graph API only at sign-up time and when we notice changes, and using cached data for the rest of the page loads.


Discussion

Updated about 2 weeks ago
Facebook © 2013 · English (US)
AboutAdvertisingCareersPlatform PoliciesPrivacy Policy