Facebook Chat over XMPP using OAuth2
26 November, 2011Unlike the MSNXMPP posts, there isn't a huge code dump in this post. Working code is up on GitHub for the XMPP Authenticator and OAuth2Helper
Like Messenger, Facebook has an XMPP gateway into their Chat system. Unlike Messenger though, Facebook XMPP is fairly mature and has multiple forms of authentication available - namely DIGEST-MD5 and X-FACEBOOK-PLATFORM. DIGEST-MD5 is fairly straightforward and isn't the focus of this post but it should be pointed out that most XMPP clients are capable of DIGEST-MD5 as it is a standard auth mechanism that relies on the username and password. This is good for Facebook support - as their docs point out, you can use DIGEST-MD5 when the client isn't even aware of Facebook.
OAuth2
The main reasons I see for using X-FACEBOOK-PLATFORM are
- You're building a Facebook chat client that also uses other Facebook data, so you want access with a single login and/or
- you don't want to store username/passwords.
Facebook were one of the first (if not the first?) major sites to adopt OAuth2, moving away from their custom token based API. The url for beginning OAuth2 requests is
https://www.facebook.com/dialog/oauth?client_id=<CLIENTID>&redirect_uri=<REDIRECT>&scope=<SCOPE>&display=touch&response_type=token"
If you're developing a desktop or mobile app that can control the browser/have an embedded browser instance, you're encouraged to use https://www.facebook.com/connect/login_success.html as the redirect uri.
For scope, you use comma separated values rather than space separated values that Messenger uses. At a minimum, you want access to xmpp_login and unless you want to refresh it every hour, offline_acccess too. If you're building a Facebook app, this is a good time to add those additional permissions. Your scope string should look something like
offline_access,xmpp_login
On success, you'll be redirected to that redirect uri with a #access_token=.. appended. Handling it is exactly the same as handling Messengers implicit flow.
webBrowser.Navigate(<FACEBOOK URL>);
webBrowser.LoadCompleted += (s, e) =>
{
if (!e.Uri.Fragment.Contains("access_token"))
return;
var responseAll = Regex.Split(e.Uri.Fragment.Remove(0, 1), "&");
for (var i = 0; i < responseAll.Count(); i++)
{
var nvPair = Regex.Split(responseAll[i], "=");
if (nvPair[0] != "access_token")
continue;
var accessToken = nvPair[1];
}
}
I think once I've nutted out Google's OAuth2 XMPP flow, I'll create a library of helpers for all three - there are enough differences that they all need customisation, but enough similarities that based helpers would be... helpful.
X-FACEBOOK-PLATFORM
Like X-MESSENGER-OAUTH2, X-FACEBOOK-PLATFORM uses OAuth2 tokens and requires an embedded webbrowser client side to process. Unlike X-MESSENGER-OAUTH2, it requires a bit of extra processing - the client must issue a request, server issues a challenge, then finally the response.
Just like any other XMPP implementation, the auth mechanisms will appear in the 'stream features' when initially connecting to chat.facebook.com.
<stream:features>
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
<mechanism>X-FACEBOOK-PLATFORM</mechanism>
<mechanism>DIGEST-MD5</mechanism>
</mechanisms>
</stream:features>
The Challenge
To begin the challenge process, you need to send what type of auth (X-FACEBOOK-PLATFORM) like this
<auth mechanism='X-FACEBOOK-PLATFORM' xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />
It's the same mechanism we sent with Messenger, except without contents, and it just starts the process. Next up the server will send a challenge. Challenges are a standard part of XMPP, so this isn't anything unusual. The only thing unusual is that it isn't a JSON or XML packet, but instead an "http-url-like" string thats URL and Base64 encoded.
<challenge xmlns="urn:ietf:params:xml:ns:xmpp-sasl">dmVyc2lvbj0xJm1ldGhvZD1hdXRoLnhtcHBfbG9naW4mbm9uY2U9OEQwRkJGRDdBQTY5RkI0REJDRjc2MzEzNkY2NjFDRUM=</challenge>
Decoded (Base64 the Url decode), that challenge looks something like:
version=1&method=auth.xmpp_login&nonce=8D0FBFD7AA69FB4DBCF763136F661CEC
You can discard the version (it'll always be 1), but the method and nonce are important. I'd say that the method will always be auth.xmpp_login, but I can't guarantee it and the documentation says to extract it.
The Response
In response, you must build up a similar string, then Base64 encode it. For what it's worth, Base64 is something XMPP dictates, not Facebook.
method=auth.xmpp_login&api_key=<APIKEY>&access_token=<ACCESSTOKEN>
To do that, I used the following code
private string BuildResponse(string method, string nonce)
{
var requestString = new StringBuilder();
requestString.AppendFormat("method={0}", method);
requestString.AppendFormat("&api_key={0}", _apiKey);
requestString.AppendFormat("&access_token={0}", Connection.UserPassword);
requestString.AppendFormat("&call_id={0}", "0");
requestString.AppendFormat("&v={0}", "1.0");
requestString.AppendFormat("&nonce={0}", nonce);
var response = EncodeTo64(requestString.ToString());
return response;
}
static public string EncodeTo64(string toEncode)
{
byte[] toEncodeAsBytes = Encoding.ASCII.GetBytes(toEncode);
string returnValue = System.Convert.ToBase64String(toEncodeAsBytes);
return returnValue;
}
Once encoded, send the response like so
<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>bWV0aG9kPWF1dGgueG1wcF9sb2dpbiZhcGlfa2V5PTxBUElLRVk+JmFjY2Vzc190b2tlbj08QUNDRVNTVE9LRU4+</response>
And (hopefully!) you should get back a success followed by regular XMPP roster/etc.
<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>
Caveats
Facebook Chat is not actually an XMPP server - or something along those lines. There is the gateway/translation process, but like Messenger it doesn't operate directly on XMPP so there are some "lost in translation" issues.
- It handles plaintext messages only (not HTML)
- XEP-0085 not XEP-0022 for typing notifications
- vCards via XEP-0054
- Just like Messenger you can't add/remove contacts via Roster management - you'd need to use the Facebook Graph API
There are a few other things explicitly not supported, but basically it can be summed up as: if it's not on this list, it probably won't work. XMPP Core is supported, but not too much else.
Is this a good thing?
At the end of my first Messenger/Xmpp post, I pondered was that implementation a good thing, concluding that 'yes but mostly no'. Facebook doesn't suffer the same response
- Facebook XMPP documentation was actually really useful
- Addition of
offline_accessscope means no renewing of tokens - Fallback to
DIGEST-MD5allows non-Facebook aware apps to connect - There is a roadmap (for Facebook APIs in general)
- We know there won't ever be VoIP/file transfer (a 'no' is better than not knowing) as Skype handles VoIP/Video.
Knowing what limitations are in place allows a more consistent app. Knowing that some of the data can be replaced with other API calls is also helpful. Messenger is a chat client/network trying to be a social network - the "homepage" of live.com is useless relying on data aggregation and GUID's for profile urls. Facebook is a social network trying to be a chat network and succeeding relatively well at it.

