Using AgFx with OAuth
New Release
First, if you wander on over to the AgFx CodePlex site, you'll see that there is a new release up. There's some new things in this new release such as some statistics about the performance of your application, the ability to specify exact cache expiration, and some bug fixes. The majority of feedback that I have gotten has been feature requests or "how do I?", and few in the way of bug reports. So it's working well for most people, hopefully that continues. Please file bugs if you find them!
All of the below references the code sample that you can find in the CodePlex source tree at AgFx\Samples\Facebook.Auth.Sample.
OAuth with Facebook and AgFx on Windows Phone 7
Something applications commonly want to do is to use Facebook as their credentials system instead of hand-rolling one. For many types of applications this makes good sense, but it turns out that integrating a Windows Phone 7 application with Facebook can be a bit challenging.
So with this release, you'll see a new sample called Facebook.Auth.Sample that gives you a template to start from that will hopefully make your life easier.
In case you're new to OAuth, it's basically a way to establish credentials without allowing the calling application to ever see a username or password. So instead of some kind of a web service call like service.login(username, password), instead you have a bit of a round-about pattern that looks like the following:
- The calling application directs a web browser to an auth url. Imagine something like https://www.somesite.com/oauth?appid=12345\&redirect\_uri=https://www.myapplication.com/authcomplete
- The user enters the SomeSite credentials into the web page and submits.
- If the credentials are correct, SomeSite issues a redirect to something like https://www.myapplication.com/authcomplete\#access\_token=ABCD1234\&expires\_in=3600.
- For mobile appliacations "authcomplete" may be a dummy URL, it's just there as away to get the values. So the application traps the Navigating event on the browser, sees the token, grabs it off of the URL, and ends the process. What the application really cares about is access_token. In the case of Facebook, they have a landing page called login_success.html that we can use without having to set up a server.
Because there isn't a direct service call here, AgFx isn't set up to really handle this process. It wants a URL to call and a response to parse. We can't do that, but it's still nice to use the caching and databinding-friendly features of AgFx, so we can craft a hybrid solution.
The strategy is basically as follows:
- Complete the process above for Facebook
- Once we have a token and expiration time, stash it on a LoadContext for our FacebookLoginModel (derives from LoginModel)
- Have AgFx kick off a Load for that load context.
- Instead of returning a WebLoadRequest, which fetches a URL, we create a custom LoadRequest (FacebookLoadRequest) that just serializes the token/expiration into a Stream and returns that.
- The result of that load will be an instance of FacebookLoginModel with the Token available for us to call, and now AgFx has data that it can manage via the regular caching mechanism.
With that in hand, we're in good shape, and we can start making other calls out to Facebook. It is a little goofy that we take data that we have in hand, write it to a stream, and just read it back out again. But OAuth itself is a bit odd compared to the rest of the direct call APIs out there. So this is the price we pay. It's a small one.
Take a look at FacebookLoginModel for details of how this dance works.
Login UI with PhoneApplicationFrameEx
PhoneApplicationFrameEx has a property on it called "PopupContent". This content will be shown over the top of the UI (e.g. over the current Page) whenever IsPopupContentVisible is set to true.
We can use this to make the login UI show automatically whenever the user is not logged in, and disappear automatically when a login succeeds. If you look in the samples App.xaml.cs, you'll see the following:
// Here we set up the frame binding.
//
// the below says:
// "When the FacebookLoginModel.Current.IsLoggedIn property is FALSE,
// set the PhoneApplicationFrameEx.IsPopupVisible to true. Or vice-versa."
//
RootFrame.PopupContent = loginView;
Binding b = new Binding("IsLoggedIn");
b.Source = FacebookLoginModel.Current;
b.Converter = new NotConverter();
RootFrame.SetBinding(PhoneApplicationFrameEx.IsPopupVisibleProperty, b);
Which basically takes the FacebookLoginModel.IsLoggedIn property and binds it's opposite to the IsPopupVisible property on the Frame. So, when the app starts for the first time, no cached creds will be available, and the login UI will show.
In the sample, the login UI is pretty much just the WebBrowser control, but I added an overlay that updates status when navigations and processing is happening.
See FacebookLoginView in the sample:
Upon logging in, the app fetches the full name of the user that logged in.
Making Service Calls
Once we are logged in, making service calls is easy. In the case of the sample, there is a very basic ViewModel called FacebookUser. By default, I just call the "me" user, which returns the information for the logged in user. It works like a standard AgFx model, and here's an example of it's GetLoadRequst:
public LoadRequest GetLoadRequest(LoadContext loadContext, Type objectType) {
// abort if we don't have a token.
//
if (!FacebookLoginModel.Current.IsLoggedIn) {
return null;
}
// build the "me" url including the auth token.
//
string meUri = String.Format("https://graph.facebook.com/{0}?access_token={1}", loadContext.Identity, FacebookLoginModel.Current.Token);
return new WebLoadRequest(loadContext, new Uri(meUri));
}
And in Deserialize, you'd typically want to use a JSON parser to pick out the data, which comes back from Facebook as JSON. For brevity in the sample, I'm using a Regex, which is not an industrial strength solution! In this case, the only value we grab is the user's full name, which then ends up bound into the UIs.
All calls you make should access the FacebookLoginModel.Current.Token property to access the access_token, and a good pattern to handle log outs and other errors is to create a base DataLoader (like FacebookDataLoaderBase, for example), that has unified handling for errors and scenarios where the token has expired. If that's the case, you have one place to go clear the login state. Which, if you may have guessed, will automatically pop the login UI. Then derive your model DataLoaders from that type and do your model-specific processing.
Challenges Along the Way
Getting this to work was a bit harder than I expected, so I'm glad I took the time to work through the scenario.
There were three issues:
- On a redirect, the Windows Phone 7 WebBrowser control has a bug that strips everything after the #. If you'll look up at the OAuth process, you'll see that there's a hash at step 3. This is a big problem because we lose the exact information we need. There is a workaround, but involves an extra redirect, and worse than that, involves needing to have the app's secret embedded in the code. I'm making sure this gets fixed for the next phone release.
- Logging out of Facebook turns out to be challenging. If you search the web for "Facebook OAuth logout", you'll find lots of long, complicated threads of people trying to solve this problem. The issue is that there isn't a clean way of killing an access_token. So what happens is that you tell AgFx to logout and clear the LoginModel state, then redirect the web browser to the oauth page, and it just logs you right back in again because of a browser cookie that's hard to get to. After a fair amount or wrestling, I managed to detect this case and then redirect to https://www.facebook.com/logout.php, wait for that to complete, then to back to the auth page. That seems to work, but I'm not in love with the solution.
- There are hiccups in the Facebook OAuth process. For example, if the user is logging in from a device for the first time, they will be directed to the Facebook page that lets you name the device. Unfortunately this doesn't perisist the redirect so the process dies. With some more code you could work around this, but if you don't, the user ends up on their home page.
The sample project handles both of these cases so you can use it as a template for your own Facebook or other OAuth integrations. I believe that Facebook uses standard OAuth, so it should port to another service cleanly, but I have not verified this. The pattern should be about the same.
Comments
- Anonymous
October 27, 2014
We are currently working on a project where we still use Windows Phone 7, so this is very useful.