How to use embedded web UI of MSAL.NET on WPF on .NET Core

This post has been republished via RSS; it originally appeared at: New blog articles in Microsoft Tech Community.

Azure AD B2C is a powerful service for providing business-to-customer identity.

https://docs.microsoft.com/en-us/azure/active-directory-b2c/overview

 

Of cause, you can also use Azure AD B2C sign feature on WPF on .NET Core. However, at now(April 17, 2020), there are few limitations:

https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/System-Browser-on-.Net-Core

 

  1. You can't use embedded web browser UI.(Have to use System browsers as default)
  2. You can't use 'http://localhost'(No port) redirect URL.(The feature will be released soon: https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/1213)

In this article, I will try implements custom web ui for embedded web browser UI for WPF on .NET Core.

How to impl?

That's really simple!
Just implements Microsoft.Identity.Client.Extensibility.ICustomWebUi interface. There is a single method:

Task<Uri> AcquireAuthorizationCodeAsync(Uri authorizationUri, Uri redirectUri, CancellationToken cancellationToken)

The return value is an URI that has code=CODE parameters.

If there are something wrong during authentication flow, then throws MsalExtensionException.

Impl a Browser Window and the interface

To show a custom web UI, create a window that has a WebBrowser control.

<Window x:Class="EmbeddedMsalCustomWebUi.Wpf.Internal.EmbeddedWebUiWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:EmbeddedMsalCustomWebUi.Wpf.Internal" mc:Ignorable="d" WindowStyle="ToolWindow" Loaded="Window_Loaded" Closed="Window_Closed" Title="EmbeddedWebUiWindow" Height="450" Width="800"> <Grid> <WebBrowser x:Name="webBrowser" Navigating="WebBrowser_Navigating" /> </Grid> </Window>

And then, implements the code behind.

using Microsoft.Identity.Client.Extensibility; using System; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Web; using System.Windows; using System.Windows.Navigation; namespace EmbeddedMsalCustomWebUi.Wpf.Internal { public partial class EmbeddedWebUiWindow : Window { private readonly Uri _authorizationUri; private readonly Uri _redirectUri; private readonly TaskCompletionSource<Uri> _taskCompletionSource; private readonly CancellationToken _cancellationToken; private CancellationTokenRegistration _token; public EmbeddedWebUiWindow( Uri authorizationUri, Uri redirectUri, TaskCompletionSource<Uri> taskCompletionSource, CancellationToken cancellationToken) { InitializeComponent(); _authorizationUri = authorizationUri; _redirectUri = redirectUri; _taskCompletionSource = taskCompletionSource; _cancellationToken = cancellationToken; } private void WebBrowser_Navigating(object sender, NavigatingCancelEventArgs e) { if (!e.Uri.ToString().StartsWith(_redirectUri.ToString())) { // not redirect uri case return; } // parse query string var query = HttpUtility.ParseQueryString(e.Uri.Query); if (query.AllKeys.Any(x => x == "code")) { // It has a code parameter. _taskCompletionSource.SetResult(e.Uri); } else { // error. _taskCompletionSource.SetException( new MsalExtensionException( $"An error occurred, error: {query.Get("error")}, error_description: {query.Get("error_description")}")); } Close(); } private void Window_Loaded(object sender, RoutedEventArgs e) { _token = _cancellationToken.Register(() => _taskCompletionSource.SetCanceled()); // navigating to an uri that is entry point to authorization flow. webBrowser.Navigate(_authorizationUri); } private void Window_Closed(object sender, EventArgs e) { _taskCompletionSource.TrySetCanceled(); _token.Dispose(); } } }

Implements AcquireAuthorizationCodeAsync method using the above window.

using EmbeddedMsalCustomWebUi.Wpf.Internal; using Microsoft.Identity.Client.Extensibility; using System; using System.Threading; using System.Threading.Tasks; using System.Windows; namespace EmbeddedMsalCustomWebUi.Wpf { /// <summary> /// Provides embedded web ui for WPF on .NET Core. /// The web ui is using WebBrowser control(Trident engine). /// </summary> public class EmbeddedBrowserWebUi : ICustomWebUi { public const int DefaultWindowWidth = 600; public const int DefaultWindowHeight = 800; private readonly Window _owner; private readonly string _title; private readonly int _windowWidth; private readonly int _windowHeight; private readonly WindowStartupLocation _windowStartupLocation; public EmbeddedBrowserWebUi(Window owner, string title = "Sign in", int windowWidth = DefaultWindowWidth, int windowHeight = DefaultWindowHeight, WindowStartupLocation windowStartupLocation = WindowStartupLocation.CenterOwner) { _owner = owner ?? throw new ArgumentNullException(nameof(owner)); _title = title; _windowWidth = windowWidth; _windowHeight = windowHeight; _windowStartupLocation = windowStartupLocation; } public Task<Uri> AcquireAuthorizationCodeAsync(Uri authorizationUri, Uri redirectUri, CancellationToken cancellationToken) { var tcs = new TaskCompletionSource<Uri>(); _owner.Dispatcher.Invoke(() => { new EmbeddedWebUiWindow(authorizationUri, redirectUri, tcs, cancellationToken) { Owner = _owner, Title = _title, Width = _windowWidth, Height = _windowHeight, WindowStartupLocation = _windowStartupLocation, }.ShowDialog(); }); return tcs.Task; } } }

Test

Create an app for WPF client on Azure AD B2C tenant as public client app and check redirect URL.

KazukiOta_1-1587107372027.png

 

And create an another app on B2C tenant, and export an API from the app, then add the permission from the client app.

KazukiOta_2-1587107525419.png

And If you haven't created sign in user flow on the tenant, then create it.

KazukiOta_3-1587107952035.png

Collect following items to use WPF app:

  • Application ID(Client ID) (GUID)
  • Tenant ID (GUID)
  • Redirect URI: it is on the Authentication page on the B2C portal.
  • Azure AD B2C Authority: https://{tenant name}.b2clogin.com/tfp/{tenant name}.onmicrosoft.com/{user flow name}.
  • Scope: It is an URL (https://{tenant name}.onmicrosoft.com/{guid}/{name}) that is able to get at API permissions page of another app.

And then create a WPF App(.NET Core) project, and create an IPublicClientApplication instance on Startup event of App class.

PublicClientApplication = PublicClientApplicationBuilder.Create("{your client id}") .WithRedirectUri("{your redirect uri}") .WithTenantId("your tenant id") .WithB2CAuthority("{your azure ad b2c authority}") .Build();

The last step! Use EmbeddedBrowserWebUi class that implemented on this article with AcquireTokenInteractive method.

var r = await PublicClientApplication .AcquireTokenInteractive(new[] { "{your scope}" }) .WithCustomWebUi(new EmbeddedBrowserWebUi(this)) // here .ExecuteAsync();

It works as below:

signinflow.gif

 

Completed source code

The custom web ui and test app codes are on following github repo:

https://github.com/runceel/EmbeddedMsalCustomWebUi.Wpf

 

If you would like to try EmbeddedCustomWebUi class on your code, then you can get it from NuGet:

https://www.nuget.org/packages/EmbeddedMsalCustomWebUi.Wpf/

 

Important:
This is a just sample code. It is not tested for production.

 

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.