I am developing a WPF application that requires me to get an Access Token from Facebook using oAuth. After much searching online, I came to the following conclusions:
- OAuth must be done in a browser
- I need to watch the URL posts in that browser, therefore it would have to be inside a WebBrowser WPF control
I decided to create a Modal Dialog for doing the Facebook authentication, and I can just take the Access Token and ignore the rest. I wanted to keep to the MVVM model but it was more difficult than I anticipated. Any ideas on how to do that would be very helpful
Here are some features that I implemented
- Cookie deletion so I could have another user authenticate without needing to log the current user out
- Disable new account creation since it led to a weird UI experience
- Listening to the cancel button from the javascript generated by Facebook
The WPF Window
The WPF is very simple. In essence it is just a WebBrowser control with the Navigated and Navigating events hooked up.
<Window x:Class="FacebookAuthenticator.FacebookAuthenticationWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Authenticate Facebook"
Height="600"
Width="600"
ResizeMode="NoResize"
WindowStyle="ToolWindow">
<Grid>
<WebBrowser Name="webBrowser"
Navigated="webBrowser_Navigated"
Navigating="webBrowser_Navigating" />
</Grid>
//The Application ID from Facebook
public string AppID {get; set; }
//The access token retrieved from facebook's authentication
public string AccessToken {get; set; }
public FacebookAuthenticationWindow()
{
InitializeComponent();
this.Loaded += (object sender, RoutedEventArgs e) =>
{
//Add the message hook in the code behind since I got a weird bug when trying to do it in the XAML
webBrowser.MessageHook += webBrowser_MessageHook;
//Delete the cookies since the last authentication
DeleteFacebookCookie();
//Create the destination URL
var destinationURL = String.Format("https://www.facebook.com/dialog/oauth?client_id={0}&scope={1}&display=popup&redirect_uri=http://www.facebook.com/connect/login_success.html&response_type=token",
AppID, //client_id
"email,user_birthday" //scope
);
webBrowser.Navigate(destinationURL);
};
}
Getting the Access Token
I forgot exactly where I got this code (if someone can remind me so that I could give proper credit I would be grateful).
private void webBrowser_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e)
{
//If the URL has an access_token, grab it and walk away...
var url = e.Uri.Fragment;
if (url.Contains("access_token") && url.Contains("#"))
{
url = (new System.Text.RegularExpressions.Regex("#")).Replace(url, "?", 1);
AccessToken = System.Web.HttpUtility.ParseQueryString(url).Get("access_token");
DialogResult = true;
this.Close();
}
}
Deleting Cookies
I realized that after someone logged in, there status stayed that way and would not allow someone else to log in. I decided to remove the cookies in the beginning of each authentication in order to prevent this.
private void DeleteFacebookCookie()
{
//Set the current user cookie to have expired yesterday
string cookie = String.Format("c_user=; expires={0:R}; path=/; domain=.facebook.com", DateTime.UtcNow.AddDays(-1).ToString("R"));
Application.SetCookie(new Uri("https://www.facebook.com"), cookie);
}
No New Accounts
Allowing the user to create a new account led to a weird UI experience. For my use cases, the user should already have an exsiting account. I disabled this by checking if the user was redirected to "r.php/" which is what Facebook uses to create a new account.
private void webBrowser_Navigating(object sender, System.Windows.Navigation.NavigatingCancelEventArgs e)
{
if (e.Uri.LocalPath == "/r.php")
{
MessageBox.Show("To create a new account go to www.facebook.com", "Could Not Create Account", MessageBoxButton.OK, MessageBoxImage.Error);
e.Cancel = true;
}
}
Handling window.close()
The cancel button in Facebook's dialog shows the ability to close the window. I needed to catch this and make sure to close the window. I had no idea how but I saw that in the MessageHook I was able to see that the last message to be sent (int msg) each time was 130, so I just listened for 130. It's sloppy, but it works.
IntPtr webBrowser_MessageHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
//msg = 130 is the last call for when the window gets closed on a window.close() in javascript
if (msg == 130)
{
this.Close();
}
return IntPtr.Zero;
}
Summary
Using the code is pretty simple:
FacebookAuthenticationWindow dialog = new FacebookAuthenticationWindow() { AppID = "YOURAPPID" };
if(dialog.ShowDialog() == true)
{
string accessToken = dialog.AccessToken;
//The world is your oyster
}