Naweed Akram's Blog
<sharing: Knowledge />
Windows 8/RT WebAuthenticationBroker view issue - Alternative
Posted: March 13, 2013

Doing oAuth in Windows 8 couldn’t have been any easier and simpler. Remember the old days of getting Access Tokens from providers such as Yammer, GitHub, Facebook, 500px etc. (you name it). You had to use browser windows, passing URLs, manage the state etc. Windows 8 make it as simple as the code below to do all the underlying work for you. All you have to do is use the WebAuthenticationBroker, which presents the oAuth dialog, manages user authorization and state, and returns the access token:

 

string redirectURI = "Some RedirectURL";
string loginURI = "The oAuth URL of the Website";

var result = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, new Uri(loginURI), new Uri(redirectURI));

if (result.ResponseStatus == WebAuthenticationStatus.Success)
{
    var response = result.ResponseData;

    //Extract the access token from reponse variable
    //Use the Access Token to extract data from website
}

 

This is all amazing. I recently started developing a Windows 8 client for Yammer. The first step was getting the access token from Yammer. I used the above code to get the Access Token.

 

It all worked fine. BUT, there was a small problem I encountered. The problem had nothing to do with the code or how WebAuthenticationBroker works. It was more on the VISUAL/UI front. The oAuth dialog that Yammer presents doesn’t fit fully in the window (some part was cut-off, specially the buttons to authorize the app). This is how the screen looked like:

 

 

 

 

 

Instead, the screen should have looked like this:

 

 

 

 

Obviously, this cannot work. If the end-users cannot see the Authorize/Allow buttons in the view, how will they authorize my app. I searched everywhere on the internet for a solution, even contacted a Microsoft techie, but no solution. I set out to handling the problem in the old way (opening/embedding browser windows, managing states and user responses etc.). I was almost done (with a working solution), when I found this wonderful article, http://vikingco.de/webbroker.html by Viking Coder, who faced a similar problem with Github oAuth dialog, and did some ground work to resolve the issue. Unfortunately, he couldn’t get it to work or find a solution. However, he was kind enough to share his half-baked code in this article. I took his code, fixed the issues that he was facing (based on my investigation) and I present to you the fully functional alternative to WebAuthenticationBroker. Full source code is present at the bottom, but I will still explain bits and pieces of the code (for more details, you can still refer to the original article by Viking Coder).

 

 

Basically, what we will do in this code is replace the call to WebAuthenticationBroker with FlexibleWebAuthenticationBroker.

 

 

//Old Line
var result = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, new Uri(loginURI), new Uri(redirectURI));

//New Line
var result = await FlexibleWebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, new Uri(loginURI), new Uri(redirectURI));

 

 

In order to achieve this, the first step is to create a view or page (call it FlexibleWebAuthView) which will have an embedded WebView. The XAML code is as follows:

 

 

    
<Page
    x:Class="WebAuthBrokerAlternative.FlexibleWebAuthView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:WebAuthBrokerAlternative"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid>
        <Grid Margin="0,100,0,100">
            <Grid.RowDefinitions>
                <RowDefinition Height="60" />
                <RowDefinition Height="20" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="800" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>

            <Grid Grid.Row="0" Grid.Column="1" Background="#FF0F2733">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="80" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>

                <Button x:Name="cancelButton" Click="CancelButtonClicked" IsEnabled="True" Grid.Column="0" Style="{StaticResource AuthBackButtonStyle}"/>
                <TextBlock Grid.Column="1" Text="Connect to Yammer Service" VerticalAlignment="Center" Style="{StaticResource AuthHeaderTextStyle}" />
            </Grid>

            <Grid Grid.Row="1" Grid.Column="1" Background="White" />

            <Grid Grid.Row="2" Grid.Column="1" Background="White">
                <WebView x:Name="wv" HorizontalAlignment="Center" VerticalAlignment="Stretch" Width="800" />
            </Grid>
        </Grid>

    </Grid>
</Page>

 

 

Then we need to handle some events that happen to this WebView control. The code-behind of FlexibleWebAuthView handles two specific events: LoadCompleted and NavigationFailed.

 

 

public sealed partial class FlexibleWebAuthView : Page
    {
        public EventHandler CancelledEvent { get; set; }
        public EventHandler UriChangedEvent { get; set; }
        public EventHandler NavFailedEvent { get; set; }

        public FlexibleWebAuthView()
        {
            this.InitializeComponent();

            Loaded += FlexWebAuth_Loaded;

            wv.LoadCompleted += wv_LoadCompleted;
            wv.NavigationFailed += wv_NavigationFailed;
        }

        void wv_NavigationFailed(object sender, WebViewNavigationFailedEventArgs e)
        {
            if (NavFailedEvent != null)
                NavFailedEvent.Invoke(e.Uri, null);
        }

        private void wv_LoadCompleted(object sender, NavigationEventArgs e)
        {
            if (UriChangedEvent != null)
                UriChangedEvent.Invoke(e.Uri, null);
        }

        private void FlexWebAuth_Loaded(object sender, RoutedEventArgs e)
        {
            wv.Width = 800;
        }


        public void Navigate(Uri uri)
        {
            wv.Navigate(uri);
        }


        private void CancelButtonClicked(object sender, RoutedEventArgs e)
        {
            if (CancelledEvent != null)
                CancelledEvent.Invoke(null, null);
        }
    }

 

 

This is it for the page which will display the oAuth dialog to the user and raise events in case of Webpage Loaded or Navigation error. Next step is to create a helper class which will replace the WebAuthenticationBroker. We call it FlexibleWebAuthenticationBroker. The functionality of this class should mimic that of the original WebAuthenticationBroker (at least to the extent needed for our requirements). The helper class is very simple. It just provides a way to call our page with embedded WebView, handle events on WebView and pass the response or error back to the calling method.

 

 

public static class FlexibleWebAuthenticationBroker
    {
        public static async Task AuthenticateAsync(WebAuthenticationOptions options, Uri startUri, Uri endUri)
        {
            TaskCompletionSource tcs = new TaskCompletionSource();

            WebAuthenticationStatus responseStatus = WebAuthenticationStatus.Success;
            string responseData = "";

            Popup p = new Popup
            {
                HorizontalAlignment = HorizontalAlignment.Stretch,
                VerticalAlignment = VerticalAlignment.Stretch,

                Width = 800,
                Height = Window.Current.Bounds.Height
            };

            var f = new FlexibleWebAuthView
            {
                Width = Window.Current.Bounds.Width,
                Height = Window.Current.Bounds.Height
            };

            f.CancelledEvent += (s, e) =>
            {
                responseStatus = WebAuthenticationStatus.UserCancel;
                tcs.TrySetResult(1);
                p.IsOpen = false;
            };

            f.UriChangedEvent += (s, e) =>
            {
                if (((Uri)s).AbsoluteUri.StartsWith(endUri.AbsoluteUri, StringComparison.OrdinalIgnoreCase))
                {
                    responseStatus = WebAuthenticationStatus.Success;
                    responseData = ((Uri)s).AbsoluteUri;
                    tcs.TrySetResult(1);
                    p.IsOpen = false;
                }
            };

            f.NavFailedEvent += (s, e) =>
            {
                if (((Uri)s).AbsoluteUri.StartsWith(endUri.AbsoluteUri, StringComparison.OrdinalIgnoreCase))
                {
                    responseStatus = WebAuthenticationStatus.Success;
                    responseData = ((Uri)s).AbsoluteUri;
                }
                else
                {
                    responseStatus = WebAuthenticationStatus.ErrorHttp;
                }

                tcs.TrySetResult(1);
                p.IsOpen = false;

            };

            p.Child = f;
            p.IsOpen = true;
            f.Navigate(startUri);
            await tcs.Task;


            return new FlexibleWebAuthenticationResult { ResponseStatus = responseStatus, ResponseData = responseData };
        }
    }

    public class FlexibleWebAuthenticationResult
    {
        public string ResponseData { get; set; }
        public WebAuthenticationStatus ResponseStatus { get; set; }
    }

 

 

With this in place, the original call to WebAuthenticationBroker can be replaced with FlexibleWebAuthenticationBroker, and the result is as we desired.

 

 

//Old Line
var result = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, new Uri(loginURI), new Uri(redirectURI));

//New Line
var result = await FlexibleWebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, new Uri(loginURI), new Uri(redirectURI));

 

 

The full source code is available (below) for further customization and improvement (if needed). And special thanks to the article by Viking Coder to help me fix the issue with WebAuthenticationBroker.

Post Comment
 *
Showing all comments


You are a complete champion, for your work with this, :)
I appreciate it very much.

Posted: Mar 25, 2016 @02:44 AM

I am trying to connect yammer in my windows 8.1 store app through Web authentication broker.

This is the code I write:

var client_id = "d5MOiWJQpW7J8nGv6KF0w";
string redirectURI = "https://www.yammer.com/client_applications";

string loginURI = "https://www.yammer.com/microsoft.com/dialog/oauth?client_id=" + client_id + "&redirect_uri=" + redirectURI + "&response_type=token";

System.Uri StartUri = new Uri(loginURI)
System.Uri EndUri = new Uri(redirectURI);

var result = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, StartUri, EndUri);

string res = result.ToString();
Its not connect the yammer server, I got the error message like this

"We can't connect to the service you need right now. Check your network connection or try this again later."

Please help me, where do i missing any steps to working it fine.

Posted: May 06, 2015 @01:21 AM

just a heads up with the current yammer oauth implicit grant flow the consent UI is provided following credential collection and not to the right of credential collection so i'm finding this flexible wab implementation wasn't required.

that said its still really interesting to understand how to create a flexible wab view layout in the event that this kind of situation arises elsewhere

Posted: Aug 10, 2014 @04:47 PM

Yes, in the sample code, just comment out this line:

<TextBlock Grid.Column="1" Text="Connect to Yammer Service" VerticalAlignment="Center" Style="{StaticResource AuthHeaderTextStyle}" />

Posted: Dec 22, 2013 @10:03 PM

Could you manage to hide the title"connecting to a server"?

Posted: Dec 22, 2013 @06:52 AM

Thank you so much for your post.It helps me a lot for my current Windows 8 project :)

Posted: Dec 01, 2013 @02:52 PM

The issue was with your Redirect URI. Plz let me know if you have any other question.

In Response to:

I am creating LinkedIn OAuth using your method. I am getting only redirect URL as response not the OAuth token. Can you please try on behalf of me ? This blog post sound great to me :)

Posted: Sep 20, 2013 @12:07 AM

I am creating LinkedIn OAuth using your method. I am getting only redirect URL as response not the OAuth token. Can you please try on behalf of me ? This blog post sound great to me :)

Posted: Sep 19, 2013 @07:24 AM

Helped me in Github page integration. Good work.

Posted: Apr 02, 2013 @04:26 AM

RSS Feed
Newsletter


Quick Links

Tags