Who Am I – A Facebook Game, Part 3

This is the third part of ‘Who Am I – A Facebook Game’. If you didn’t already, check out part 1 and part 2.
This post’s code can be found on the whoami repository on github.

Where Were We?

In the first part we introduced Who Am I – a Facebook game that helps you get a better understanding of your personality through friends feedback.
In the second part we developed all the basic functionality of the game using Python and Flask.

In This Post

In this post we are going to register our game on facebook.
We’ll go over the steps needed to register a game and configure it.
While at it, we will introduce ngrok – a tool that will allow you to host the game on your computer.
We will then integrate our game with the facebook API so it can use the player’s facebook data such as her name, photo and friends list.
Finally we are going to create test users and test our game.

What is a Facebook Application?

Basically, a facebook application is an application that uses the facebook API to interact with facebook in various ways. For example, an application can get a user’s profile photo to display it in a scores board, or get the user’s friend list in order to personalize his experience. An application can also post on a user’s wall and send invites to his friends.
To do all this, the application must get authorized by the user.

The Facebook API

A facebook application interacts with facebook through the Graph API. It is a rest API that models all the data in facebook as a graph.
The nodes of the graph are things like users, photos and pages. Edges connect nodes.
For example, a user is connected to the photos he is tagged in. Nodes also have fields that store data about the node, for example, a user’s first and last name or a page’s creation date.
To use the graph API, an application does not need a username or password. Instead it uses access tokens.
An application needs 2 tokens to access the graph API:

  1. Its own token. This is the application’s secret which is generated when you register the application to facebook.
  2. A user token. The user token is generated when the application requests it from facebook. If this is the first time the application asks for that user’s access token, facebook will ask the user authorize the application before giving the token. The token will only allow the application to do what the user authorized it to do.

You can read more about tokens on the Access Tokens page.

Canvas Applications

The above definition for a facebook app is very broad. Your application could be a web application that uses facebook to personalize the user’s experience or a mobile app that lets a user interact with his facebook friends.
Our game, Who Am I, is a facebook canvas application. It means it is a web application that is displayed inside the facebook site.
The general architecture looks like this:
Facebook Canvas Architecture
When the user clicks on the application link in his home page, facebook displays a page with an iframe. Around the iframe is facebook content coming from the facebook server – the search bar on the top and the news feed on the right.
The iframe gets its content from the application’s server. You configure the exact url the iframe should display in the application settings page in facebook.
This is a very simple architecture. It is also possible to use Facebook’s Javascript SDK. In this case, the application’s canvas will also communicate with the facebook server.

Generic Startup Workflow

Canvas application have a generic startup workflow. It goes like this:

  1. Facebook user clicks a link to the application
  2. Facebook opens page with an iframe. The iframe does a POST request to the application’s URL. The request contains information about the user.
  3. The application checks the information in the request. If it contains a valid user token then all is good. It can start using that token to interact with facebook.
  4. If the request does not contain a token, it means the user did not authorize the application yet. The application redirects to facebook’s application authorization dialog. When redirecting, the application sends facebook a list of permissions it requests and a URL to redirect to once the user approves.
  5. Facebook displays the application authorization dialog to the user. It displays the permissions the application asked for. If the user approves, facebook sends a POST request with a valid token to the URL provided by the application.

In the next sections we will transform WhoAmI to a facebook application step by step.

Create a Facebook Developer Account

A facebook developer account extends your facebook account with access to developer tools. It will allow you to create new facebook applications that interact with facebook through the facebook API.
Follow the instructions on facebook to create a developer account.

Create a New Facebook Application

Login to Facebook and browse to your facebook home page. If you created a facebook developer account you should see a “Developers” section at the bottom of the left side bar:
facebook-sidebar
Click the “Create App..” link. If it doesn’t appear, hover over the “Developer” caption and click the “More” link.
Facebook will display the “Create a New App” dialog. Fill in the required details:
Create a New App Dialog

Fill in the application display name and namespace.
The display name is of course the name that will be displayed for your application in Facebook.
The namespace will be used to build your application’s canvas page URL in Facebook. To get to your application, your players will browse to https://apps.facebook.com/<namespace>. So if you’re app namespace is whoami, its URL will be https://apps.facebook.com/whoami.
This URL is called the application’s Canvas Page.
Once you submit the new app dialog you will be taken to the app settings where you can configure the app.

Configure the Application

Let’s take a look at the app page. By default, facebook shows the app dashboard:
Application Dashboard
It shows the App ID and App Secret. You will need these numbers to use the Facebook Graph API.
Below that are stats about the application’s usage.
Click the settings link on the left side of the page to get to the app settings page:
App Settings 1
The top part of the page shows basic information about the application – the app id, secret, display name, etc.
We now need to tell facebook that we want our game to be accessible through the facebook site as a canvas application.
Click the big “Add Platform” button and in the dialog that is displayed select “App on Facebook”.
A new settings section is added to the page:

App Settings 2

These are the settings in the dialog:

  1. Canvas Page – This is a readonly field which shows the URL to the application on facebook. It is built using the namespace of the application.
  2. Unity Integration – Unity is a game engine that helps you build multi platform graphical games. This setting does not interest us so we’ll leave it off (no).
  3. Canvas URL and Secure Canvas URL – this is the URL to our application, the one the iframe in the facebook page will show. Here you need to provide the url where your application is hosted. If you host WhoAmI on a host accessible to the internet, you can write it’s url here. If you don’t have such a host (because you’re behind NAT or a firewall) or your ISP assigns you a dynamic IP, fear not! We’ll explain how to use ngrok to make your app accessible from facebook.
  4. Canvas Fixed Width – If you set this to “Yes” then the iframe width will be fixed to 760px. You can leave it off right now.
  5. Canvas Fixed Height – setting this to “Yes” will allow you to set the canvas height using facebook’s JS API. Leave it off.

HTTP Tunneling Using ngrok

If you want to host your app from your local host, you can do it using ngrok.
ngrok will give you an external URL. Requests to the external URL will be routed by ngrok to your local host and your app responses will be sent back to the client.
Note that this is of course great from development or testing but don’t use it for production systems.
To use ngrok, download the ngrok client from ngrok site. Extract the client somewhere and run it passing port 5000 as the parameter:

ngrok 5000

ngrok will display a status screen. The top of the screen displays some information including the external HTTP and HTTPS URLs that ngrok assigned to your application. They look something like http://deec5050.ngrok.com.
Run your application and browse to its external URL.
That’s it!
Now you can copy-paste the ngrok URLs to your app settings on Facebook.

Login to Facebook

If you browse to your apps URL (https://apps.facebook.com/<namespace>) you will see a “Method Not Allowed” error. This is because like we described above, the first request facebook does to your app is a POST request with the user’s token info.
Our app is not yet ready to accept POST requests to its root URL.
To handle facebook’s handshake and use facebook’s graph API we will use facebook’s python sdk.
The sdk is on github.
Download the SDK. To make things simple, just copy the facebook.py script to your project’s root folder.
Now let’s do some magic and login to Facebook!
Start by importing the facebook sdk:

import facebook

And add your app details as configuration parameters to the whoami.py script, next to the other constants:

DATABASE = 'db/whoami.db'
DEBUG = True
SECRET_KEY = 'top secret'
APP_SECRET = '<app secret>'
APP_ID = '<app id>'
CANVAS_PAGE = '<app canvas page>'

You need to replace the placeholder values with the real values from your app’s settings page on facebook.
Remember that the following call: app.config.from_object(__name__) reads all upper case attributes of the module and makes them available by calling, e.g. app.config['APP_SECRET'].
Now to do the login we need to process POST requests to our root URL.
If this is the first time the user uses the application, facebook will not send the app the user’s token. In this case, we need to redirect the user to a dialog that let’s him authorize our app. If the user already authorized our app, the POST request will contain a user token.
Change the main() and home() functions, note that we also changed the app.route() decorator to accept POST requests:

@app.route('/', methods=['POST', 'GET'])
def main():
    return home()


@app.route('/home')
def home():
    if request.method == 'POST':
        if not read_user_token():
            return build_authenticate_redirect()
    elif not session.get('user_id'):
        return redirect('https://apps.facebook.com/<namespace>')
    window = calc_window()
    return render_template('home.html', window=window)

*(Note that you need to replace the <namespace> placeholder with your app’s namespace)*
The only change to main() is to accept POST requests. The actual logic happens in home().
If current request is done using the POST method, we assume it’s facebook sending us the login information. We try to read the user token from the request using read_user_token(). If the request contains a user token, read_user_token() will set 3 session variables – user_id – the user’s facebook id, oauth_token – the user token, expires – when does the user token expires.
If the user has not authorised the application yet, the request will not contain a user token and read_user_token() will return False.
In this case, we redirect to facebook’s authorize app dialog using build_authenticate_redirect.
If the current request was not a POST request, we check if the user is already logged into the application. If not, then we redirect the user to the application’s canvas page to let facebook take care of login (i.e. re-send the initial POST request).
Finally, if everything is ok and we have the user’s token and id, we continue to the application code itself, in this case, to render the personality page.
Now let’s take a look at the code that does the actual authentication. Add the following functions to your script:

def build_authenticate_redirect():
    auth_url = 'https://www.facebook.com/dialog/oauth?%s' % (
        urllib.urlencode({'client_id': app.config['APP_ID'], 'redirect_uri': app.config['CANVAS_PAGE'],
                          'scope': 'user_friends'})
    )
    return make_response("<script> top.location.href='" + auth_url + "'</script>")


def read_user_token():
    if request.method == "POST" and request.form.get('signed_request'):
        fb_request = facebook.parse_signed_request(request.form['signed_request'], app.config['APP_SECRET'])
        if u'user_id' in fb_request:
            session['user_id'] = fb_request[u'user_id']
            session['oauth_token'] = fb_request[u'oauth_token']
            session['expires'] = fb_request[u'expires']
            return True
        else:
            return False

    if session.get('user_id'):
        expires = int(session[u'expires'])
        now = time.time()
        if expires > now:
            return True

    return False

In order for this code to work, you will need to import time, urllib and from flask import make_response.
Let’s explore what this code does. We’ll start with the read_user_token() function.
It verifies that indeed we have a POST request and that the request form data contains the field signed_request. If all is well, it proceeds to parsing the signed_request field using facebook sdk – facebook.parse_signed_request(). The signed_request field contains base64 encoded json object, signed with the app secret. This means only the app developer (who is the only one who knows the app secret), can read the data.
This is important since the data can contain the user token which was given specifically to this application and we don’t want any other application to get a hold of this token.
The call to facebook.parse_signed_request() decodes the data into a dictionary.
Note that all strings in the facebook API’s json objects are unicode strings. So whenever you’re using these strings to query a dictionary object, make sure to use python’s unicode string notation – u'some unicode string'. This is not necessary in python 3.0 where strings are unicode by default. But we’re using python 2.7 so we need to use the special notation.
If the user already authenticated the application, the signed_request field will contain the user id, the user’s token and the expiry time of the token. In this case we store these variables in the user session and we’re done – the user is ‘logged in’.
If the signed_request field does not contain the necessary fields, it means the user did not authenticate our application yet (most probably this is his first access to the application). In this case we return False.
Last, if we got here not as part of the login process, but on some subsequent request, we check if the token we have is still valid. Facebook token expire after some time and in this case the application need to re-request the token.
This is it for the read_user_token() function.
Remember that if read_user_token() returns False, we need to redirect to facebook’s authentication dialog.
build_authenticate_redirect() function builds the response that will make this happen.
The facebook authentication dialog looks like this:
Authenticate Dialog
To display this dialog, build_authenticate_redirect() uses javascript to redirect the entire page (not just the iframe) to the authenticate dialog url, passing the app id, the app canvas page and the required permissions.
The authenticate dialog explains to the user that he is logging into an application and which permissions the application requests.
If the user clicks the “Play Game” button, Facebook will redirect to the application canvas page with a POST request. This time the POST request will contain the user’s token and our game can read it and finish the login process.
If the user clicks the “Cancel” button, Facebook will also redirect to the application canvas page but instead of sending the user’s token, it will send some URL parameters explaining the problem.
To make sure we gracefully handle a “Cancel” click, we need to add some code to the home() function:

    if 'error_reason' in request.args:
        return "Sorry, if you want to play 'Who Am I' you need to authorize it"
    elif request.method == 'POST':
        if not read_user_token():
            return build_authenticate_redirect()
    elif not session.get('user_id'):
        return redirect('https://apps.facebook.com/d_who-am-i')
    window = calc_window()
    return render_template('home.html', window=window)

Now we test if the request contains an error_reason parameter. If it does, we assume that the user clicked “Cancel” in the authenticate dialog and display a suitable message. Of course there might be other situations where facebook send this parameter but we won’t handle them now.

Testing the Login Flow

Let’s see if the login flow works.
Run your application, make sure ngrok is running if you need it and browse to your app’s canvas page. Remember you can see your app’s canvas page in the app’s settings page.
You should see the facebook authorization dialog. Click “Play Game” and you should be taken to WhoAmI home page. The number in the header of the page is your user id.
Let’s check that the cancel authorization flow works. Revoke WhoAmI authorization. You can do this by clicking on Games under Apps on the left side of your Facebook home page. This will open the App Center. Click My Apps on the left side bar of the App Center and delete WhoAmI. It will not delete the app itself, only de-authorize it for your user.
Now browse again to your app’s canvas page and this time on the authorization dialog, click Cancel. You should see the message from WhoAmI requesting you to authorize the app.
Note: As I was writing this post, Facebook has updated the home page. All the instructions remain the same but visually the interface might look a bit different than the screenshots. Also it seems that deleting the application by clicking on the X symbol in the application box in the App Center does not work. You need to click on Settings and then click on Remove.

Yes, We’re Done Logging In

This concludes the Facebook login flow.
Go make yourself some coffee and come back for the next step.

Getting User Information

Now that we’ve logged in, we can get information from facebook about the user.
We will retrieve the user name and the profile picture and display them in the personality view. Edit the home.html template, replace the first header with this:

    <img src="{{ profile.pic_url }} "/>
    <h1>Hello {{ profile.first_name }}</h1>

This code expects to have a profile variable with 2 values – a url for the user’s profile picture and the user’s first name.
To provide this we create a new function to get these (and other interesting) values from facebook and use this function in home():

def get_user_profile(user_id):
    graph = facebook.GraphAPI(session['oauth_token'])
    result = graph.get_object(user_id, fields='id,name,picture.type(large),first_name,last_name')
    #   {
    #       "id": "...",
    #       "name": "Doron Tohar",
    #       "picture": {
    #           "data": {
    #               "url": "https://...",
    #               "is_silhouette": false
    #           }
    #       }
    #   }
    profile = {
        'id': result[u'id'],
        'name': result[u'name'],
        'first_name': result[u'first_name'],
        'last_name': result[u'last_name'],
        'pic_url': result[u'picture'][u'data'][u'url']
    }
    return profile

@app.route('/home')
def home():
    if 'error_reason' in request.args:
        return "Sorry, if you want to play 'Who Am I' you need to authorize it"
    elif request.method == 'POST':
        if not read_user_token():
            return build_authenticate_redirect()
    elif not session.get('user_id'):
        return redirect('https://apps.facebook.com/d_who-am-i')
    window = calc_window()

    profile = get_user_profile(session['user_id'])

    return render_template('home.html', window=window, profile=profile)

Let’s examine this code.
We defined a new function – get_user_profile() which accepts one parameter – the user id. This way we will be able to use this function later to get the profile for any user that we have his id.
The first line in get_user_profile() initializes the graph API using the user token. Because the token contain exactly the permissions we asked for in the authorization dialog, we will only be able to use API calls that doesn’t require other permissions. If we try for instance to post on the user’s wall, we will get an exception because we can’t do that with the token we have.
Next line is the call to graph.get__object(). This fetches the object with the id that we passed to it. Since we pass a user id, we get back a user profile. If we passed a facebook post id, we would get back information about the post.
The second parameter to graph.get_object() is the fields parameter. If we omit this parameter (it is optional), facebook will return the a default set of the object’s fields (not all of them).
Using this parameter we can control exactly which fields we get back. This is good because we don’t want to get fields that we don’t need (overhead) and sometimes the default field set does not contain a field we’re interested in.
The return value of the API call is a json object containing the requested information. The facebook parses the json object to a dictionary. From this dictionary we construct a new dictionary, replacing unicode strings with regular strings to make it easier to access it.
The only thing left to do is to pass this profile object to the render_template() call that renders the home.html page.

The Graph Explorer

In order to explore the graph API, Facebook provides the Graph Explorer.
In the graph explorer application, you can perform graph api calls using the REST interface. It has a UI that shows you the fields of the object you requested.
For example, to get the syntax for getting the large profile picture for a user, I did a GET request for my user id. I then selected the ‘picture’ connection from the field selection box and then selected ‘type’ and ‘large':
Graph Explorer

Leftovers

This is for this part. Before we summarize and talk a little about the next part, some leftover fixes that we need to do:

Remove login() and logout()

Now that we’re logging in through facebook, we don’t need to the login() and logout() functions.
You can remove them and also remove the logout link from the home.html template. If you don’t remove the link you will get an error when Jinja2 tries to render the template.

Move the DB To a Persistent Location

We’ve put the DB in /tmp. Let’s move it under our project folder so it won’t get accidently deleted. Create a db folder under your project folder and create the db there (I hope by now you know how).

Use a Persistent ngrok Subdomain

Everytime you restart ngrok, it will create a new subdomain for you. Sometimes it will be the same as the last subdomain you’ve got, sometimes it will be different. If it’s different then you will need to re-configure your canvas URL in Facebook.
To avoid this hassle, you can tell ngrok which subdomain to use. But to do this you first need to sign up to ngrok. You can sign up for free but if you’re using this tool, please consider paying for it. You can pay whatever you want! You gotta appreciate people with this kind of flexible pricing policy.
Once you sign up to ngrok, follow the instructions in the sign up page and documentation to setup a persistent subdomain.

Summary

In this part we finally connected to Facebook. We went through the Facebook login flow (which is the same no matter what programming language you use) and then used Facebook’s graph API to get the player’s name and picture and displayed them in the main page.

Next Post

In the next post we will enable the player to use facebook invites to ask her friends to fill the friend test for her.
This post’s code can be found on the whoami repository on github.

About these ads

8 thoughts on “Who Am I – A Facebook Game, Part 3

  1. Hi, still following your post. I got a Facebook API error when testing the login flow:
    API Error Code: 191
    API Error Description: The specified URL is not owned by the application
    Error Message: Invalid redirect_uri: Given URL is not allowed by the Application configuration.
    It turns out to be an issue with redirect_uri in function “build_authenticate_redirect”. If we replace CANVAS_PAGE with canvas URL as the value of redirect_uri, the problem will be resolved. Hope this will help someone having the same issue like me :)

      • My CANVAS_PAGE is “https://apps.facebook.com/know_yourself_better” and my canvas URL is “http://whoami.ngrok.com/”. I think both of them should be good. However, if I pass CANVAS_PAGE to redirect_uri, I’ll get the API error I mentioned before. If I pass canvas URL, everything will be OK. Any thoughts?

  2. Hi wei,
    As far as I can tell, after the user authenticates the application, Facebook redirects the entire page to the url that was passed in the redirect_uri parameter. So the only valid value there should be the canvas page. If you want you can paste your build_authenticate_redirect code in a comment and I will take a look.

    • Sure, my codes are as follows:

      def build_authenticate_redirect():
      auth_url = ‘https://www.facebook.com/dialog/oauth?%s’ % (
      urllib.urlencode({‘client_id': app.config[‘APP_ID’],
      ‘redirect_uri': app.config[‘CANVAS_PAGE’],
      ‘scope': ‘user_friends’})
      )
      return make_response(” top.location.href='” + auth_url + “‘”)

      The codes above will give me API Error Code: 191. My current fix is changing app.config[‘CANVAS_PAGE’] to app.config[‘CANVAS_URL’] (suggested by “http://stackoverflow.com/questions/7382621/facebook-api-error-code-191“). However, the Chrome will pop out a prompt window saying “This page includes script from unauthenticated sources” and give me an option of “load unsafe script”. If I click it, I’ll be redirected to authentication page. After clicking “Play Now”, I could see the main page of our WhoAmI page, but I noticed that there is a red diagonal slash across HTTPS in the URL and the session in our code seems not to be working.

  3. Hi daramasala,

    Here I am again :) Just wonder if you got an issue of session when redirecting from home page to ‘self-test’ page. It seems like the session won’t work after redirecting.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s