Live Messenger over XMPP
21 November, 2011
Microsoft's Windows Live Messenger uses a custom protocol known as "MSNP", now up to at least version 21 of the protocol. Every client that supports Messenger - up until recently - has used a version of this protocol. The only problem with MSNP is that to use it, you need to reverse engineer Microsoft's work and given significant parts of it are encrypted, it results in a cat-and-mouse game of trying to decrypt, understand, and then support the new features, often resulting in bungled implementations.
Recently, however, Microsoft announced that they'd have XMPP access to Messenger contacts/IM. Does this mean you can plug Messenger server details into any XMPP client? Not exactly. It does mean with a little bit of modification, you could support multiple IM networks via a single client a lot easier - working on one protocol/plugin (even with a few variants) is easier than maintaining netcode for two distinct protocols.
Authorization
Instead of plugging in your username and password (SASL-PLAIN or SASL-DIGEST-MD5), Messenger over XMPP uses OAuth2 and a custom SASL method - X-MESSENGER-OAUTH2. For reference, Google have X-GOOGLE-TOKEN and Facebook X-FACEBOOK-PLATFORM for their XMPP implementations, but they both have fallbacks to allow you to use your username/password. OAuth2 is significantly easier than OAuth1 to consume - you won't need any encryption or OAuth helper libraries to handle it as there isn't any secret/token generation required to consume.
First you need to sign up and "create" an application through the Live Connect control panel - there isn't any cost, its just a simple registration for a Client ID.
Then you need to use a browser control (or browser window with redirect back to a webapp) to display
https://oauth.live.com/authorize?client_id=CLIENTID&redirect_uri=https://oauth.live.com/desktop&response_type=token&scope=SCOPES
At a minimum, you need the scope to be set to wl.messenger, but wl.offline_access will work too (either by itself, or combine the scopes separating them with a space). CLIENTID in this case is the Client ID generated when you signed up and created your Live Connect app. Once the user logs in and authorises, you'll be redirected to a page with the query fragment (that is, stuff after a #) containing access_token. You need to store that token.
Hint: if you want a finger-mash friendly page, make sure you include display=touch.
Show me the (C#) code!
Whether you use WinForms, WPF, WP7 or something else, you need to generate a request url to pass to a browser, and peek at the response Uri.
public static class OAuth2Helper
{
public static Uri GenerateRequestUrl(string clientId, string scope, string display = "touch")
{
return new Uri(String.Format(@"https://oauth.live.com/authorize?client_id={0}&display={2}&redirect_uri=https://oauth.live.com/desktop&response_type=token&scope={1}", clientId, scope, display));
}
public static string AccessTokenFromUriResponse(Uri response)
{
if (!response.Fragment.Contains("access_token"))
return string.Empty;
var responseAll = Regex.Split(response.Fragment.Remove(0, 1), "&");
return (from t in responseAll
select Regex.Split(t, "=") into nvPair
where nvPair[0] == "access_token"
select nvPair[1]).FirstOrDefault();
}
}
To start the OAuth2 process:
webBrowser.Navigate(OAuth2Helper.GenerateRequestUrl("12354", "wl.messenger"));
To get the token back:
webBrowser.LoadCompleted += (s, e) =>
{
if (!e.Uri.Fragment.Contains("access_token"))
{
var authToken = OAuth2Helper.AccessTokenFromUriResponse(e.Uri);
}
}
SASL
It took a little while for me to get the hang of it as I was implementing it from scratch, but custom SASL implementations can be very straight forward. After the initial XMPP connection is established, you'll get back a "features" list from the server that will look similar to this (although without the linebreaks and indentation)
<stream:features xmlns:stream="http://etherx.jabber.org/streams">
<starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls">
<required />
</starttls>
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
<mechanism>X-MESSENGER-OAUTH2</mechanism>
</mechanisms>
</stream:features>
If that doesn't make sense to you, TLS/SSL is required, and X-MESSENGER-OAUTH2 is the only SASL mechanism that is supported - you cannot use MD5-DIGEST or PLAIN auth. Your response should be
<auth mechanism='X-MESSENGER-OAUTH2' xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>OAUTH_TOKEN_GOES_HERE</auth>
And if that was a valid token, you'll receive back <success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" /> which (should) be followed by roster and presence information. Make sure that you Uri decode the token!. Other reasons why it might reject your token include an expired token (more on that below), insufficient privledges (ie, you set the scope to wl.basic) or a revoked token.
Most XMPP/SASL connect libraries will have a way to pass in a name (X-MESSENGER-OAUTH2) and string/token.
Limitations
According to the documentation
The Windows Live Messenger Extensible Messaging and Presence Protocol (XMPP) service supports only these extensions as defined by the XMPP Standards Foundation, with partial support for these extensions as noted.
RFC6120: XMPP: Core
RFC6121: XMPP: Instant Messaging and Presence. Roster management is not supported.
XEP-0054: vcard-temp. The Messenger XMPP service supports fetching vCards but doesn't support updating vCards.
XEP-0085: Chat State Notifications.
XEP-0203: Delayed Delivery
The emphasis is mine, but it highlights how this is a partial solution to using XMPP to connect to Messenger - there is no VoIP, there is no video, there is no file transfer. Even forgetting those parts (most third party implementations of MSNP lack those three features anyway), lack of roster management and only partial vCard support is problematic - it isn't as simple as an authentication layer being put in front to get it to work, XMPP roster management (that is, adding/removing contacts) would have to be done via the REST API (if those exist - I haven't looked).
Currently I'm experiencing "idle" issues - after 1-2 minutes of no traffic, the connection is closed by the other end. Is this an issue with their XMPP server? XEP-0199 isn't specifically mentioned as supported (thats "ping/pong" in XMPP standards) although it does have references throughout RFC4120, but it's only the Messenger XMPP server that drops the connection (against my code that is).
Token Expiry
This is a big one, and fundamentally breaks the way any existing XMPP client works. Unlike OAuth1, OAuth2 tokens are designed to be short lived and in this case, they expire after an hour. This doesn't mean the connection is dropped after an hour, it just means next time you go to log in, if it's been greater than an hour, you'll need to get a new token.
Facebook uses OAuth2 for it's "Graph Protocol", so how does that get around mobile/desktop apps that don't renew? By requesting offline_access during the initial OAuth2 stages, that sets the token to never expire. Access can still be revoked, but the token won't timeout.
Tokens can be renewed, the SDK documents tell me, but I haven't figured out how. You're suppose to send a request to
https://oauth.live.com/token?client_id=[CLIENTID]&client_secret=[CLIENTSECRET]&redirect_uri=https://oauth.live.com/desktop&grant_type=refresh_token&refresh_token=[REFRESHTOKEN]
The client ID and secret are found on the Live Connect control panel, but I'm not sure what the refresh token is - neither (access_token and authentication_token) tokens returned by the initial authorisation process seem to work.
Ultimately, I think the Facebook approach of having long-lasting tokens when required would have been better in this situation.
Is this a good thing?
While I'm happy to see a more standard IM protocl AND a standard authentication protocol be adopted/permitted, I'm not entirely sold on this particular implementation of XMPP.
- There isn't a (public) roadmap for future features (if there will be any),
- the changes required aren't as simple as the initial authentication,
- the documentation and samples don't actually cover .NET (and I don't really feel like setting up a Java dev environment just to see if their samples are "stable" on Java),
- Token expiry reaaaallly sucks for any compatibility with existing XMPP implementations
This experiment wasn't a complete loss. I'll still be using MSNPSharp, but the changes made to Hanoi (our XMPP library) and MahChats were needed anyway - that is, inheriting from a base Xmpp plugin to create preconfigured "profiles" (ie, Facebook, GTalk then generic XMPP/Jabber) and the code Hanoi was based on (BabelIM) didn't allow custom SASL mechanisms to be easily plugged in - they had to be hard coded in.

