Skip to content

Facebook Login using native control

craigomatic edited this page Aug 26, 2014 · 4 revisions

If you've tried creating a Hybrid WebApp with a website using Facebook login, you've probably experienced the pain of clicking that Login with Facebook button only to see your app launch into an IE window, thus destroying any opportunity to control the outcome of the login.

Fear not as there is a way to overcome this, broken down by platform below.

Note that this tutorial assumes you're using the HybridWebView Control. It also assumes that the website you're working with uses the Facebook SDK for JavaScript.

To be successful, we need to do the following:

  1. Add a mapping to the appropriate route, override window.open
  2. Handle the KnownMessageTypes.WindowOpen message
  3. Proxy the result of the authentication back to the website

Windows 8.1 using WebAuthenticationBroker

Step 1: Add a mapping to the appropriate route

Add a handler for the Ready event on the HybridWebView control:

<toolkit:HybridWebView x:Name="WebHost" WebUri="http://www.example.org" Ready="HybridWebView_Ready" />

In the code-behind, add the following to the HybridWebView_Ready handler:

private void HybridWebView_Ready(object sender, EventArgs e)
{
    WebHost.WebRoute.Map("/", async (uri, success, errorCode) =>
    {
        await WebHost.Interpreter.EvalAsync("framework.overrideWindowOpen();");
    });
}

This will rewrite window.open and cause it to propagate a message back up to the host app.

Step 2: Handle the KnownMessageTypes.WindowOpen message

Add a handler for the MessageReceived event on the HybridWebView control:

<toolkit:HybridWebView x:Name="WebHost" WebUri="http://www.example.org" Ready="HybridWebView_Ready" />

In the code-behind, add the following to the HybridWebView_MessageReceived handler:

private async void HybridWebView_MessageReceived(HybridWebView sender, ScriptMessage args)
{
    switch (args.Type)
    {
        case KnownMessageTypes.WindowOpen:
            {
                var navItem = await JsonConvert.DeserializeObjectAsync<NavItem>(args.Payload);

                var windowOpenUri = new Uri(navItem.Href);

                if (!windowOpenUri.OriginalString.StartsWith("https://www.facebook.com/dialog/oauth"))
                {
                    await Windows.System.Launcher.LaunchUriAsync(windowOpenUri);
                }
                else
                {
                    await _StartFacebookAuthFlow(windowOpenUri);
                }

                break;
            }
    }
}

The above will check if it's a Facebook login request or something else. In the case it's something else, the URI is simply passed through to the Launcher which will open the default browser at that location.

Next, we need to implement the _StartFacebookAuthFlow method which passes control over to the WebAuthenticationBroker to accept the user's credentials:

private async Task _StartFacebookAuthFlow(Uri requestUri)
{
    try
    {
        var appId = "<your_facebook_app_id>";
        var callbackUri = new Uri("https://www.facebook.com/connect/login_success.html");
        var newRequestUri = new Uri(string.Format("https://www.facebook.com/dialog/oauth?client_id={0}&redirect_uri={1}&display=popup&response_type=token", appId, callbackUri.ToString()));

        var result = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, newRequestUri, callbackUri);

        if (result.ResponseStatus == WebAuthenticationStatus.UserCancel)
        {
            return;
        }

        var needle = "access_token=";
        var tokenStartIndex = result.ResponseData.IndexOf(needle) + needle.Length;
        var tokenEndIndex = result.ResponseData.IndexOf("&", tokenStartIndex);
        var accessToken = result.ResponseData.Substring(tokenStartIndex, tokenEndIndex - tokenStartIndex);

        await WebHost.Interpreter.EvalAsync(string.Format("app.fbLogin('{0}');", accessToken));
    }
    catch
    {

    }
}

Step 3: Proxy the result of the authentication back to the website

This line from the previous step is our bridge back to the website:

await WebHost.Interpreter.EvalAsync(string.Format("app.fbLogin('{0}');", accessToken));

This step will typically require some investigation of what the website does with the access token from Facebook.

In general, you'll need a proxy function to pass the access token back and complete the login:

app.fbLogin = function(accessToken) {
    //TODO: 
}

The exact JS required to do this will vary by website and is outside of the scope of the docs.

Windows Phone 8.x Silverlight using Facebook SDK for .NET

Step 1: Add a mapping to the appropriate route

Add a handler for the Ready event on the HybridWebView control:

<toolkit:HybridWebView x:Name="WebHost" WebUri="http://www.example.org" Ready="HybridWebView_Ready" />

In the code-behind, add the following to the HybridWebView_Ready handler:

private void HybridWebView_Ready(object sender, EventArgs e)
{
    WebHost.WebRoute.Map("/", async (uri, success, errorCode) =>
    {
        await WebHost.Interpreter.EvalAsync("framework.overrideWindowOpen();");
    });
}

This will rewrite window.open and cause it to propagate a message back up to the host app.

Step 2: Handle the KnownMessageTypes.WindowOpen message

Add a handler for the MessageReceived event on the HybridWebView control:

<toolkit:HybridWebView x:Name="WebHost" WebUri="http://www.example.org" Ready="HybridWebView_Ready" />

In the code-behind, add the following to the HybridWebView_MessageReceived handler:

private async void HybridWebView_MessageReceived(HybridWebView sender, ScriptMessage args)
{
    switch (args.Type)
    {
        case KnownMessageTypes.WindowOpen:
            {
                var navItem = await JsonConvert.DeserializeObjectAsync<NavItem>(args.Payload);

                var windowOpenUri = new Uri(navItem.Href);

                if (!windowOpenUri.OriginalString.StartsWith("https://www.facebook.com/dialog/oauth"))
                {
                    await Windows.System.Launcher.LaunchUriAsync(windowOpenUri);
                }
                else
                {
                    await _StartFacebookAuthFlow(windowOpenUri);
                }

                break;
            }
    }
}

The above will check if it's a Facebook login request or something else. In the case it's something else, the URI is simply passed through to the Launcher which will open the default browser at that location.

Next, we need to implement the _StartFacebookAuthFlow method which passes control over to the Facebook SDK for .NET to accept the user's credentials.

Install the NuGet package for Facebook.Client from the Package Manager Console into your project with the following command:

Install-Package Facebook.Client -pre

private async Task _StartFacebookAuthFlow(Uri requestUri)
{
    try
    {
        var appId = "<your_facebook_app_id>";
        var fb = new Facebook.Client.FacebookSessionClient(appId);
        var result = await fb.LoginAsync();

        await WebHost.Interpreter.EvalAsync(string.Format("app.fbLogin('{0}');", result.AccessToken));
    }
    catch
    {

    }
}

Step 3: Proxy the result of the authentication back to the website

This line from the previous step is our bridge back to the website:

await WebHost.Interpreter.EvalAsync(string.Format("app.fbLogin('{0}');", accessToken));

This step will typically require some investigation of what the website does with the access token from Facebook.

In general, you'll need a proxy function to pass the access token back and complete the login:

app.fbLogin = function(accessToken) {
    //TODO: 
}

The exact JS required to do this will vary by website and is outside of the scope of the docs.