Search code examples
pythonfacebookfacebook-appstornadofacebook-canvas

Facebook Canvas App - Circular Login Redirect


I'm using the Python based Social Cookbook template to create a Facebook App, but I'm having a problem with Canvas support which does a POST instead of a GET. The Cookbook example doesn't include how to handle this. Based on reading this Hello World example and looking at the Run With Friends example, I'm able to get the signed request, read the data (user id, token), and set the method to GET.

However, as it continues, the Browser / Tornado Server go into a loop where it repeatedly runs the LoginHandler. Giving me an error "Firefox has detected that the server is redirecting the request for this address in a way that will never complete." I've been trying to figure this out for two days and thought if anyone could help - it would be StackOverflow. Thanks for any guidance you could provide in modifying the Social Cookbook to support the Facebook Canvas.

class BaseHandler(tornado.web.RequestHandler):

    def initialize(self):
        self.init_facebook()

    def init_facebook(self):
        # initial facebook request comes in as a POST with a signed_request
        signed_request = self.get_argument('signed_request', None)
        if signed_request and self.request.method == u'POST':
            app_secret = options.facebook_app_secret
            data = load_signed_request(signed_request, app_secret)
            user_id = data.get(u"user_id")
            mytoken = data.get(u"oauth_token")
            print mytoken
            self.set_secure_cookie("uid", user_id)
            self.request.method = u'GET'  # causes loss of request.POST data

Solution

  • Ok, so here is what I ended up doing (thanks to some assistance from oDesk - Haiming Yin) and what some of my issues were. For one, the system running FireFox on the Mac had Third Party Cookies disabled. This will cause problems with the Facebook Canvas. On IE, you have to set the proper P3P headers. So all this combine made for a good headache.

    class BaseHandler(tornado.web.RequestHandler):
        @property
        def prepare(self):
            self.set_header('P3P', 'CP="HONK"')
    
        def initialize(self):
            if self.request.full_url() == "http://mydomain/a/facebook/":
                self.request.protocol = "https"
            self.init_facebook()
    
        def init_facebook(self):
            """Sets up the request specific Facebook and User instance"""
    
            # initial facebook request comes in as a POST with a signed_request
            signed_request = self.get_argument('signed_request', None)
            if signed_request and self.request.method == u'POST':
                app_secret = options.facebook_app_secret
                data = load_signed_request(signed_request, app_secret)
                user_id = data.get(u"user_id")
                if user_id:
                    self.set_secure_cookie("uid", user_id)
                self.request.method = u'GET'
    
    class FacebookCanvasHandler(HomeHandler):
        def get(self, *args, **kwds):
            logging.info("Facebook Canvas called.")
            if not self.current_user:
                logging.info("Need user grant permission, redirect to oauth dialog.")
                logging.info(self.settings.get("facebook_canvas_id"))
                oauth_url = self.get_login_url(self.settings.get("facebook_canvas_id"))
                logging.info(oauth_url)
                self.render("canvas_oauth.html", oauth_url=oauth_url)
            else:
                super(FacebookCanvasHandler, self).get(*args, **kwds)
    
    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
    
    def base64_url_decode(data):
        data = data.encode(u'ascii')
        data += '=' * (4 - (len(data) % 4))
        return base64.urlsafe_b64decode(data)
    

    canvans_oauth.html

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
            "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:fb="http://www.facebook.com/2008/fbml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>
            Page Title
        </title>
        <meta name="description" content="description of the page" /><meta name="keywords" content="" /><meta name="viewport" content="width=device-width" />
        <link rel="icon" type="image/png" href="/static/favicon.ico" />
        <script>
            window.top.location = "{% raw oauth_url %}";
        </script>
    
    </head>
    
    <body id="inner_body" class="inner_body">
    redirecting to oauth...
    </body>
    </html>