In my previous post, I talked about the app-id/secret-id pair you get when you register your app to Facebook. The documentation provided by Facebook, however, is a little bit unclear about the usage model of this pair under various SDKs. In this post, I am going to write some tips for App Engine developers who are building Facebook applications using this app-id/secret-id pair.
In a nut shell,
(1) It is possible to build your application only with JavaScript SDK. In this case, you don't need a secret key to access Facebook API.
(2) This JavaScript-only above approach is fine for client-only application (such as a small widget application that presents Facebook news feed for the user), but not appropriate if you want to use Facebook API as the authentication/identification mechanism to access your database (such as per-user storage).
(3) The best approach is a hybrid application, which uses JavaScript API for log-in and various non-critical API calls (such as fetching the list of friends to present it to the user), but uses Python SDK for authentication and some critical API calls.
I built fruence.com with this hybrid architecture. Here are some code from it:
main.js (client-side):
window.fbAsyncInit = function() {
SNBinder.get('/api/appid', null, true, function(result, data) {
FB.init({appId: result.appId, status: true, cookie: true,
xfbml: true});
FB.Event.subscribe('auth.login', function(response) {
main.init();
});
FB.Event.subscribe('auth.logout', function(response) {
main.login();
});
main.init();
});
};
SNBinder.get is a wrapper of HTTP-GET and fetches the app-id by accessing /api/appid, then calls FB.init() to initialize the API. Notice that you don't need the secret-key in this case, and you don't want to (otherwise, it is no longer a secret).
When the user clicks the log-in button (provided by Facebook) and authorizes the access to the app (this UI will be provided by Facebook as well), the application receives the 'auth.login' event.
Then (within the main.init()), the application accesses '/api/user' with a cookie attached by Facebook API, which will execute the following code on the server side.
gdispatch.route(lambda: ('/api/user', ApiUserHandler))
class ApiUserHandler(webapp.RequestHandler):
@login_required
def post(self):
self.response.out.write('{"success":true, "result":%s}' % self.current_user.json)
The login-required decorator performs the server-side authentication.
def login_required(original_func):
def decorated_func(rh, **kw):
if User.current_user(rh):
return original_func(rh, **kw)
else:
return rh.response.out.write('{"success":false, "login_required":true}')
return decorated_func
User.current_user is defined as follows.
class User(db.Model):
....
@classmethod
def current_user(cls, rh):
if not hasattr(rh, "current_user"):
rh.current_user = None
(id, secret) = cls.facebook_ids(rh)
cookie = facebook.get_user_from_cookie(rh.request.cookies, id, secret)
if cookie:
key_name = "fb:"+cookie["uid"]
user = cls.get_by_id(key_name)
if not user:
graph = facebook.GraphAPI(cookie["access_token"])
profile = graph.get_object("me")
user = cls(key_name=str(key_name),
name=profile["name"],
profile_url=profile["link"],
access_token=cookie["access_token"],
cached_logs=[])
user.put()
else:
if user.access_token != cookie["access_token"]:
user.access_token = cookie["access_token"]
user.put()
rh.current_user = user
return rh.current_user
Notice that you need to give the secret-id to Facebook API (facebook.get_user_from_cookie) in this case, but your secret-id is secure because this code is on the server side.