A big part of building a new web application is repurposing common patterns, one such pattern is the ability for users to sign in and out. One way of solving this quickly is by using Facebook Connect.
Background
Unlike some APIs, the Facebook API is very Javascript friendly, but unfortunately it can be very time consuming to go through the maze of misdirected Facebook documentation. So in order to make Facebook integration quick and easy, I've wrapped a lot of under-the-hood code into a plugin called node-facebook. This plugin also provides examples and routines to get your Facebook Canvas application running quickly with Node, however, this article will focus on the FB Connect part.
Facebook's developer tools are increasingly going into the direction of more Javascript on the client-side. I also have a strong preference of offloading certain logic to the client-side. This article will also attempt to follow that direction.
Communication
As you can see in this totally unnecessary diagram, most of the integration takes place on the client-side:
Image may be NSFW.
Clik here to view.
Dependencies
Note: For this article I've been using NodeJS version 0.1.31 and Express version 0.7.1
You need to install both NodeJS and the Express Web Framework. Assuming you've installed NodeJS, you can easily include express into your Git project by adding a submodule:
mkdir -p lib/support
git submodule add git://github.com/visionmedia/express.git lib/support/express
cd lib/support/express
git submodule init
git submodule update
Second, you need to include Hashlib into your project and compile it. Hashlib is a library that provides cryptographic routines like MD5:
git submodule add git://github.com/brainfucker/hashlib.git lib/support/hashlib
cd lib/support/hashlib
make
1. Registering your Facebook Application
In order to use Facebook Connect, you need to register a new Facebook application and set the FB Connect URL to the root of your application:
Image may be NSFW.
Clik here to view.
2. Setting up your Project
For Facebook integration you need to place these three files into your project folder:
- facebook.js - plugin for the Express framework - to be placed in /lib
- jquery.facebook.js - a simple jQuery plugin to interface with the Facebook JS library - to be placed in /public/javascripts
- xd_receiver.htm - used by Facebook for opening up a Cross-domain Communication Channel - to be placed in /public
After adding the dependencies and placing these files, your directory structure should look like this:
myproject
|-- app.js /* new file */
|-- lib
| |-- support
| | |-- express
| | `-- hashlib
| |-- facebook.js
`-- public
|-- index.html /* new file */
|-- xd_receiver.htm
`-- javascript
`-- jquery.facebook.js
To make our application work, we only need to implement two files: index.html and app.js. That's right, we're only using AJAX calls and static files.
3. In the Browser
The provided jQuery plugin provides the following functions that we'll be using:
- fbInit - initialize the JS lib and set up the cross-communication channel
- fbConnect - invoke the connect procedure and to synchronize sessions and profile information with our backend
- fbLogout - logout from both the Facebook Application and our NodeJS application
- fbIsAuthenticated - check whether a user is logged in or not
First we start out with a simple skeleton that loads jQuery and the Facebook JS library. Please note that you need the div named fb-root right after the body tag for Facebook's lib to work:
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script type="text/javascript" src="/javascripts/jquery.facebook.js"></script>
</head>
<body>
<div id="fb-root"></div>
<script type="text/javascript" src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php"></script>
</body>
</html>
Now let's implement a basic UI:
<!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">
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script type="text/javascript" src="/javascripts/jquery.facebook.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$.fbInit('FACEBOOK_API_KEY');
// FB Connect action
$('#fb-connect').bind('click', function () {
$.fbConnect({'include': ['first_name', 'last_name', 'name', 'pic']}, function (fbSession) {
$('.not_authenticated').hide();
$('.authenticated').show();
$('#fb-first_name').html(fbSession.first_name);
});
return false;
});
// FB Logout action
$('#fb-logout').bind('click', function () {
$.fbLogout(function () {
$('.authenticated').hide();
$('.not_authenticated').show();
});
return false;
});
// Check whether we're logged in and arrange page accordingly
$.fbIsAuthenticated(function (fbSession) {
// Authenticated!
$('.authenticated').show();
$('#fb-first_name').html(fbSession.first_name);
}, function () {
// Not authenticated
$('.not_authenticated').show();
});
});
</script>
</head>
<body>
<div id="fb-root"></div>
<script type="text/javascript" src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php"></script>
<div id="my-account">
<div class="authenticated" style="display: none">
Hi there <span id="fb-first_name"></span>!
<a href="#" id="fb-logout">Logout</a>
</div>
<div class="not_authenticated" style="display: none">
<a href="#" id="fb-connect">Connect with Facebook</a>
</div>
</div>
</body>
</html>
4. On the Server
The Express plugin is initialized like any other plugin in the environment configuration routine, but takes your Facebook API key and Secret as mandatory initialization arguments:
use(require('facebook').Facebook, {
apiKey: 'FACEBOOK_API_KEY',
apiSecret: 'FACEBOOK_API_SECRET'
})
Next we need to implement 3 AJAX actions to make jquery.facebook.js work:
- GET /fbSession - Is the current user logged in? Or is there a cookie/param present I can use to authenticate?
- POST /fbSession - Update additional information about the user (name, picture, etc)
- POST /fbLogout - Called after logout from the Facebook Application took place
Here is an example Express application that uses no persistent storage:
require.paths.unshift(__dirname + '/../../lib')
require.paths.unshift(__dirname + '/../../lib/support/express/lib')
require.paths.unshift(__dirname + '/../../lib/support/hashlib/build/default')
require('express')
require('express/plugins')
configure(function(){
use(MethodOverride)
use(ContentLength)
use(Cookie)
use(Session)
use(Logger)
use(require('facebook').Facebook, {
apiKey: 'FACEBOOK_API_KEY',
apiSecret: 'FACEBOOK_API_SECRET'
})
set('root', __dirname)
})
// Called to get information about the current authenticated user
get('/fbSession', function(){
var fbSession = this.fbSession()
if(fbSession) {
// Here would be a nice place to lookup userId in the database
// and supply some additional information for the client to use
}
// The client will only assume authentication was OK if userId exists
this.contentType('json')
this.halt(200, JSON.stringify(fbSession || {}))
})
// Called after a successful FB Connect
post('/fbSession', function() {
var fbSession = this.fbSession() // Will return null if verification was unsuccesful
if(fbSession) {
// Now that we have a Facebook Session, we might want to store this new user in the db
// Also, in this.params there is additional information about the user (name, pic, first_name, etc)
// Note of warning: unlike the data in fbSession, this additional information has not been verified
fbSession.first_name = this.params.post['first_name']
}
this.contentType('json')
this.halt(200, JSON.stringify(fbSession || {}))
})
// Called on Facebook logout
post('/fbLogout', function() {
this.fbLogout();
this.halt(200, JSON.stringify({}))
})
// Static files in ./public
get('/', function(file){ this.sendfile(__dirname + '/public/index.html') })
get('/xd_receiver.htm', function(file){ this.sendfile(__dirname + '/public/xd_receiver.htm') })
get('/javascripts/jquery.facebook.js', function(file){ this.sendfile(__dirname + '/public/javascripts/jquery.facebook.js') })
run()
The verification of Facebook data by the server-side is done by using the Application Secret and the signature that's sent along with the data. First, all parameters and cookies are put together in one string and then the Application Secret is appended to it. The MD5 hash of this string should match the signature that's included. more about verifying the signature
In any subsequently added action, you can access the Facebook Session simply like this:
get('/hello', function () {
var fbSession = this.fbSession()
return 'Hello ' + ' user ' + fbSession.userId + '!';
})
6. Further Development
In this article we went into the direction of putting a lot of UI flow and controller logic into the browser. This can be quite counter-intuitive. As a Rails-programmer and former RJS lover, I can attest to that. However, while there are still remaining issues like SEO and accessibility, this approach allows the server to really focus on data modelling/routing and has numerous scaling benefits.
All examples in this article and more can be found on the node-facebook repository I created. If you run into any obstacles, feel free to contact me or fork the code. I hope to soon write a similar plugin for Twitter's OAUTH based login.
Appendix A: Facebook Troubleshooting Checklist
Debugging Facebook Application problems can be a real pain in the neck, here is a simple checklist distilled from many frustrating mind-cycles:
- Are you sure xd_receiver.htm is in place and being accessed?
- Are you sure the element is present in the body?
- If you are using Safari with iFrames, there are some cookie hacks you need to do
- Are cookies being set successfully after FB connect?
- Are you sure you're using the correct API keys?