Wednesday, July 6, 2011

Follow-up on Hello World Facebook App

Introduction

As a proof that I'm still learning how to make a Facebook App, I found some flaws in my previous post. The flaw is that it only works for cases wherein the user accessing the app has already authorized the app. I discovered this when I tinkered with test users in facebook. So below is the updated version.

Lets Digress, How to Create Facebook Test Users

I only discovered the problem with the first version of the application after testing it with some test users. Thus in my opinion, it is very, very important that we put importance to the creation of test users to be able to test our applications properly.

First we need to generate an access token.


https://graph.facebook.com/oauth/access_token?
     client_id=YOUR_APP_ID&client_secret=YOUR_APP_SECRET&
     grant_type=client_credentials

You can use curl or your browser to make a GET request to the URL above. I just used  a chrome browser. Use the access token to generate the test user by making a request to the following URL.

https://graph.facebook.com/APP_ID/accounts/test-users?
  installed=false
  &permissions=read_stream
  &method=post
  &access_token=APP_ACCESS_TOKEN

The generated test user is specific to the application being tested so you have to provide some information regarding the application to be tested in the URL above.  Also take note of the 'installed' parameter. If it is set to 'false', it means that the generated user has not yet installed/authorized the application. Making a GET request to the above URL would return a JSON string similar to the following.


{
   "id": "11111111",
   "login_url": "https://www.facebook.com/platform/test_account_login.php?user_id=11111111&n=pU4qXlyHOobnAX7",
   "email": "ebbzkuk_huiman\u0040tfbnw.net",
   "password": "1111111"
}

To use the generated user, simply cut and paste the login_url information to the navigation bar of a browser and you can start testing your application.






Updated Hello World Program


import tornado.ioloop
import tornado.web


import base64
import json
import hmac
import hashlib
import time
import urllib2
import urllib


APP_SECRET = '####'
APP_ID = '####'


def base64_url_decode(data):
    data = data.encode(u'ascii')
    data += '=' * (4 - (len(data) % 4))
    return base64.urlsafe_b64decode(data)


def load_signed_request(signed_request, app_secret):
    try:
        sig, payload = signed_request.split(u'.', 1)
        sig = base64_url_decode(sig)
        data = json.loads(base64_url_decode(payload))


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


        if sig == expected_sig and data[u'issued_at'] > (time.time() - 86400):
            return data
        else:
            return None
    except ValueError, ex:
        return None


class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")


    def post(self):
        signed_request = self.get_argument('signed_request')
        data = load_signed_request(signed_request, APP_SECRET)


        if data.get('user_id'):
            user_id = data.get(u"user_id")
            url = 'https://graph.facebook.com/%s'%user_id
            fd = urllib2.urlopen(url)
            response = fd.read()
            fd.close()
            userdata = json.loads(response)
            self.write('Hello %s\n'%userdata['name'])
        else:
            canvasurl = urllib.quote("http://apps.facebook.com/nielsampleapp/")
            url = "https://www.facebook.com/dialog/oauth?client_id=%s&redirect_uri=%s&scope=email,read_stream"%(APP_ID, canvasurl)
            self.write('<script> top.location.href="%s"</script>'%url)


application = tornado.web.Application([
    (r"/", MainHandler),
])


if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

The main difference in this version is that it has integrated some code for authorization. Remember that when Facebook loads the application, it makes a POST request passing a 'signed_request' parameter. The 'signed_request' parameter contains is a signed JSON string containing information about the user accessing the application.


        signed_request = self.get_argument('signed_request')
        data = load_signed_request(signed_request, APP_SECRET)

If the user has already authorized the application, the JSON string would be similar to the following JSON string.

{u'user_id': u'11111111', u'algorithm': u'HMAC-SHA256', u'expires': 1309536000, u'oauth_token': u'############', u'user': {u'locale': u'en_US', u'country': u'ph', u'age': {u'min': 21}}, u'issued_at': 1309531299}

If the user has not authorized the application, the JSON string would be similar to the following JSON string.


{u'issued_at': 1309957831, u'user': {u'locale': u'en_US', u'country': u'ph', u'age': {u'min': 21}}, u'algorithm': u'HMAC-SHA256'}

The main difference between the JSON strings above is that one has personal information, while the other doesn't. Because the main reason for making Facebook Apps is to provide a personalized user experience, it is imperative that the application should prompt the user for authorization. This is done in the following code.

                   if data.get('user_id'):
            user_id = data.get(u"user_id")
            url = 'https://graph.facebook.com/%s'%user_id
            fd = urllib2.urlopen(url)
            response = fd.read()
            fd.close()
            userdata = json.loads(response)
            self.write('Hello %s\n'%userdata['name'])
        else:
            canvasurl = urllib.quote("http://apps.facebook.com/nielsampleapp/")
            url = "https://www.facebook.com/dialog/oauth?client_id=%s&redirect_uri=%s&scope=email,read_stream"%(APP_ID, canvasurl)
            self.write('<script> top.location.href="%s"</script>'%url)

To detect whether the user has already authorized the application, the existence of the 'user_id' parameter is checked in the passed JSON data. If 'user_id' does not exist, it means that the user has not yet authorized the application. To facilitate the authorization process, the application redirects the user to a Facebook dialog which handles the authorization process.

            canvasurl = urllib.quote("http://apps.facebook.com/nielsampleapp/")
            url = "https://www.facebook.com/dialog/oauth?client_id=%s&redirect_uri=%s&scope=email,read_stream"%(APP_ID, canvasurl)
            self.write('<script> top.location.href="%s"</script>'%url)




After authorizing the application, the user is redirected back to the application.

No comments:

Post a Comment