Connecting with Facebook
There are several ways to integrate your application with Facebook: you can have a standalone application (which uses Facebook for authentication but has a UI totally separate from Facebook apart from login), and there can be two flavours of that, a "desktop" experience and a "mobile" one; or you can create a "canvas application" in which your page appears within the Facebook UI. This latter is what we opted for, to try to leverage some of the social aspects of Facebook for spreading a game out to friends, for example.
The way the canvas is implemented in the Facebook ecosystem is that Facebook launches your application in an iframe within the Facebook UI, and - conveniently - when your application is first invoked, the user is already logged into Facebook. That final point is quite important: it means we don't have to deal with any of the complexity of logging in, with its redirects between different pages and callbacks from Facebook!
When you register an application with Facebook (see Facebook's documentation or do a web search, I'm not going to describe the process here), you get an application id and secret key for use in your code, and you specify the URL to your entry page under the canvas settings: that's the URL Facebook will load into the iframe when the user launches the game. Since some time in October, Facebook has required that canvas applications provide SSL connections, which means that your application needs a suitable certificate - see the MSDN documentation for how to add a certificate to an Azure deployment, for example. However, if the application is in "sandbox mode" you can still access it over HTTP, which is convenient for debugging. (When an application is in the sandbox, only people you've registered as developers or testers can see it.)
As I mentioned in the first post in the series, I'm using the Facebook C# SDK, incorporated into my project via NuGet - that package add process inserts the appropriate line in web.config for the application id and secret, which is quite convenient (though when you update the package, you get another copy of that line, so be sure to check your web.config after updrades). It also adds a FacebookInit.cshtml view which you can incorporate in your main view file(s). I'm definitely not any sort of expert on how Facebook is, or should be, initialized, but it looks like not all of the stuff in FacebookInit.cshtml is required for canvases. The initialization has two parts: a call to FB.Init, and then a handler for the completion for the authentication process, the default action of which is to reload the page, presumably to update any internal state when login has completed. As I said before, with canvas apps, you're already authenticated when the canvas loads, so there's no need to reload - and so I just removed that handler.
At this point I should mention that there are two distinct Facebook login contexts: on the client, and on the server. Some applications may use one or the other; we use both. On the client, roughly speaking, Facebook grants the logged in user to his/her own data, and to whatever other users make public; on the server, it's possible to perform other operations (assuming that the user grants permission). We could have done everything we wanted on the server but we chose to do as little Facebook work as we could there - mainly, to be honest, to reduce the load on our service, but also to reduce the chance of any data leak of Facebook data (if we don't have it, we can't leak it). For example, we want to show the name and image of your opponent when you play a game: we could request such data from Facebook within the service, and then pass that on to the client; or we could let the client ask Facebook directly. The second of those takes a little load off us and reduces latency a little in that it's a single hop to Facebook and not a bounce via us. (Now, were we targeting a mobile platform, with potentially slow network connections, it may be better to perform the Facebook lookups within the server, with its fast connection to Facebook, and provide everything the client needs in one call instead of one call to us and then one more more additional ones to Facebook. Yes, we're passing pretty much the same amount of data overall to the client in both cases, but we're removing the need to set up multiple connections in sequence, thus reducing latency.)
I've already outlined pretty much all that we do with Facebook on the client, getting hold of some user data for display. Within the server we do quite a lot more. As I detailed in the last blog post, most of the traffic between client and (our) server is in the form of game service calls of one sort or another; one exception is creating the canvas iframe HTML in the first place. I'll cover the contents of that in more detail in a future post, but suffice to say we have an Index.cshtml view file and an associated controller entry which looks like:
[CanvasAuthorize(Permissions = "publish_actions,offline_access")]
public ActionResult Index()
{
var viewModel = CreateIndexModel();
return View(viewModel);
}
The body of the method is pretty boring, it's the attribute above it that's more interesting: that indicates that (server side) authorization is required, and that we're asking for a couple of permissions from the user - the ability to publish scores, and the ability to send messages to them (from other players), respectively. That's pretty much all that's required when using the Facebook C# SDK and this automatically presents the user permissions dialog box. (One thing we could have done better here is not ask for these permissions up front: we should have had an entry page that introduces the game and explains why we needed these permissions instead of the first thing the user sees being the permissions dialog. We could also have introduced a mode such that the game could be played without needing any non-default permissions. Material for the next game, perhaps.)
Here's a simple example of a routine getting some Facebook data on the server:
private List<string> GetFriends()
{
var fb = new FacebookWebClient();
dynamic fbResults = fb.Get("me/friends?fields=id");
var fbFriendIds = new List<string>();
foreach (dynamic f in fbResults.data)
fbFriendIds.Add(f.id);
return fbFriendIds;
}
The FacebookWebClient object makes use of the previously authenticated connection to Facebook, and is then used to execute a Graph API call into Facebook. The form of the result that this call returns depends on the call and using "dynamic" really does end up with the least code to write. A bit of experimentation and a lot of reading of Facebook documentation was required to get to this point - with a fair amount of time on Stack Overflow too. (Oh, for better Facebook and Facebook C# SDK documentation!)
Here I have to confess that there's something I just don't understand about how the Facebook C# SDK authenticates... The method used in that routine above was good for getting friend data and a few other things. However, it failed when trying to post scores - I thought that, having requested the permissions from the user, the access token created by the SDK would be good for that, but it seems not. This Stack Overflow post seemed to have the answer, and here's how I ended up posting scores:
private FacebookClient GetAppAccessClient()
{
var url = string.Format("https://graph.facebook.com/oauth/access_token?client_id={0}&client_secret={1}&grant_type=client_credentials",
Facebook.FacebookApplication.Current.AppId, Facebook.FacebookApplication.Current.AppSecret);
string token;
using (var wc = new WebClient())
{
var response = wc.DownloadData(url);
var accessToken = System.Text.Encoding.ASCII.GetString(response);
token = accessToken.Split('=')[1];
}
return new FacebookClient(token);
}
private void PostScore(string userId, int score)
{
var client = GetAppAccessClient();
client.Post(String.Format("{0}/scores", userId), new { score = score });
}
GetAppAccessClient uses the technique in that Stack Overflow article to get an access token that does work, and then hands back a suitable client object (the same sort of thing as FacebookWebClient), which is then used for the Graph API call.
The rest of the interactions with Facebook are pretty much the same as either of the above techniques. One additional feature to implement is the deauthorization callback, mentioned in the middle of the Facebook authentication document. When a user removes or blocks your application from their Facebook page, Facebook will post to the deauthorization URL with the user's id: on receipt, your application should remove that user's data and generally tidy up. Our callback looks like:
[OutputCache(Duration = 0, VaryByParam = "*", Location = System.Web.UI.OutputCacheLocation.None)]
public JsonResult Deauthorize(string signed_request)
{
FacebookSignedRequest sr;
if (FacebookSignedRequest.TryParse(Facebook.FacebookApplication.Current.AppSecret, signed_request, out sr))
Engine.Instance.DeleteUserRecord(sr.UserId);
return Json("deauthorized", JsonRequestBehavior.DenyGet);
}
(The return value is actually irrelevant here, but helped us a little during testing.)
In this and the previous post, I think I've covered most of the server side of the application. Next time I'll have a look at the client.