The Facebook platform offers a great deal of functionality, and I have been using it recently for user authentication and management for a web application. Last December, Facebook implemented their mandatory migration to OAuth 2.0, causing breaking changes for those of us naïve enough not to monitor their blog. As part of this migration, they changed both their cookie format and policy, which will make life more difficult for server-side developers.
To access the Facebook Graph API, you need the access token. If you use Facebook's client-side SDK, you just put in an <fb:login-button> tag in your web page, and the JavaScript will emit a login button for you. This is the client-side flow, but you can have the client-side SDK generate a cookie that provides the server with Facebook access. The old cookie format (named fbs_appId)used to contain the access token, so the server could just grab that token off the cookie and use it to make requests to Facebook.
The new cookie format is a little different. Besides a different naming convention (fbsr_appId), it no longer contains the raw access token. It consists of 2 parts, a signature and a payload, separated by a period and base64 URL encoded. The payload can be verified by doing a SHA-256 hash on the payload, using the application secret as a key, and comparing that hash to the signature. The payload itself is JSON containing a "code" property. You submit this "code" (the authorization grant, in OAuth-speak) and Facebook application ID and secret (the client credentials) to Facebook to get the OAuth access token. From this point on, the server can use the access token to access whatever protected resources (such as the user profile) that the Facebook user has granted the server.
This new cookie format seems an improvement in terms of security. Since the access token is no longer stored in the cookie, you don't have to worry about it being exposed over the network. The code can only be used with your application secret, which is never sent to the browser. Unfortunately, Facebook not only neglected to document this new cookie, but they are discouraging other developers from using this cookie. This is supposed to be an implementation detail, which Facebook can change at any time. So for those of us not using their PHP SDK, how are we supposed to proceed?
On the blog, Douglas Purdy of Facebook outlined the following options (in his words):
- Make the call on the client side using the FB.api method the JS SDK.
- Get the access token from the JS SDK and pass it to the server yourself (FB.getAccessToken). Make sure you are doing that over a secure link.
- Just do the auth on the server, rather than using the JS SDK. It is hyper-simple.
Unfortunately, none of these options are acceptable. A server cannot trust anything that comes from the client (nix options 1 and 2). Option 2 now requires a secure link where we previously did not. And doing the "auth" on the server does nothing for the client.
Facebook officially gives you a server protocol, and a few client-side SDKs. The only server SDK they support is their PHP SDK. If you're not using PHP on the server, you're out of luck. The trouble with doing everything only on the browser (JavaScript SDK) or only on the server is that web applications span both sides. You want the user to log in on the browser to support Faceboko social plugins like the comments widget, "like" button and the login/logout button. You also want server access to get trusted resources direct from Facebook, as you can't trust anything coming from the browser. But you don't want to force the user to log in twice, so some sort of login sharing is desirable.
It seems the cookie solution is ideal for seamless browser/server Facebook access. Obviously, Facebook thinks so, as they are using this method for their own PHP SDK. Unfortunately, they don't seem to want others to use the same method: "do as I say, not as I do". I'm not really sure what approach I should take at this point. On one hand, I don't want to use an unsupported method. On the other, the cookie method is so much easier, and Facebook can't really introduce a breaking cookie change without also breaking their own PHP SDK. Figuring the cookie format by reading the PHP SDK code is not hard. In fact, the facebook-graph-plugin for Grails already implements this cookie handling.
What approach would you take?
it's true the documentation is very poor, incomplete and unclear. But as for now, if you set the cookie with jssdk, then all you need to do to fetch the user data (and so the access token) in phpsdk is calling the method getUser(). it took me 2 days to understand that.. I hope they'll keep this working during the future updates
ReplyDelete"A server cannot trust anything that comes from the client" - I think this is a bit too generalised. The access token is essentially similar to the user credentials (username and password), so not "trusting" it would not make sense. The whole point of the token is to establish trust.
ReplyDeleteIn the case of the "implicit" OAUTH login flow, you should indeed validate that the token was requested for your application and not another, whenever you get a new token.