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.
|