Azure AD Authentication in WPF Application using MSAL

Azure AD Authentication in WPF Application using MSAL

In this article, let’s try to setup authentication in .NET Core WPF application. Let’s use Azure AD and MSAL for this setup.

I promise there are going to be some interesting findings.

Azure AD

In this section, let’s have look at what the configurations required to register a WPF application. Please note that although the app registrations would be performed in below steps, the actual WPF application has not been created yet.

App Registration

Sign in to Azure portal and go to the top right corner and Select Azure Active Directory from the left navigation.

Next, go to App registrations and then click on New registration to register the WPF app in Azure.

Enter below inputs:

  • Name, a display name for the app registration
  • Supported account type, to confirm which accounts can be used to login to the application. Keep it to default for this demo.
  • Platform Configuration, make sure you select client application (Web, iOS, Android, Desktop + Devices option.

Then click on Register button to register our new application in Azure AD.

Register WPF App in Azure AD
Register WPF App in Azure AD

Know More about Redirect URI

The redirect URI is an interesting input here. As we know, we are trying to register a WPF application, not a web application. So, how can it have an URI ?

So, if you take a look at the documentation here, it tells us how the redirect URIs should be set for the UWP, .NET Framework and .NET Core applications. Below is the image from the documentation

If you use .NET framework application, the application opens a popup which shows login screen from Azure AD. But things are not the same for .NET Core application.

For .NET Core application, the system browser is used for redirecting user to Azure AD login screen. It means when we create WPF application, and we click on login button from our application, it will open the default browser on our machine (either edge or chrome, or Mozilla, whatever is the default browser is).

I am talking about Microsoft.Identity.Client v4.14.0. And as per documentation, for .NET Core, we are setting the value to the local host to enable the user to use the system browser for interactive authentication since .NET Core does not have a UI for the embedded web view at the moment.

Additional limitations are documented here.

How does it work ?

MSAL needs to listen on on http://localhost:port and intercept the code that AAD sends when the user is done authenticating.

So, in app registration, we can specify http://localhost in the redirect URI without any port in it. Then while setting up the Public Client Application, we need to specify the same.

MSAL will find the random available port and will use it.

Application Authentication

Now, let’s open the newly configuration application “Sample Wpf App” from the app registrations list. Then select Authentication option from the left navigation. Then select Add a platform button.

This button will open new panel on right side, asking for type of platform. Select the Mobile and Desktop applications from the options.

Configure platforms in Azure AD App Registration
Configure platforms in Azure AD App Registration

Then a new panel will open which will ask to select the redirect URI. Instead of selecting any check boxes, enter a custom URI. Enter http://localhost in the custom redirect URI text box. Then click on Configure button.

Redirect URI setting for WPF App registration
Redirect URI setting for WPF App registration
Note, that we are using HTTP scheme and not HTTPS, because it is not supported yet.

Then hit Save button to save these configurations.

WPF App

In Visual Studio, create a .NET Core WPF application. In this application, we need to setup few things:

In this demo, we will try to display the details of the token (like the user for whom it was issued and its expiry time), just to make sure that even though the authentication is happening outside, the application still gets the token.

App Configurations

In this app, I am just going to hard code few constants, for holding the authority information, client id, user flow names, redirect URIs.

Then using those values, PublicClientApplication instance is being created in below code snippet. All of the above things are present in app.xaml.cs.

public partial class App : Application
{
private static string ClientId = "89866898-d443-442e-ad58-21b6b308a520";
private static string Tenant = "da41245a5-11b3-996c-00a8-4d99re19f292";
private static string Instance = "https://login.microsoftonline.com/";
public static IPublicClientApplication PublicClientApp { get; private set; }
static App()
{
PublicClientApp = PublicClientApplicationBuilder.Create(ClientId)
.WithAuthority($"{Instance}{Tenant}")
.WithRedirectUri("http://localhost")
.Build();
TokenCacheHelper.Bind(PublicClientApp.UserTokenCache);
}
private static void Log(LogLevel level, string message, bool containsPii)
{
string logs = ($"{level} {message}");
StringBuilder sb = new StringBuilder();
sb.Append(logs);
File.AppendAllText(System.Reflection.Assembly.GetExecutingAssembly().Location + ".msalLogs.txt", sb.ToString());
sb.Clear();
}
}
view raw app.xaml.cs hosted with ❤ by GitHub

Token Cache

The token cache implementation below, simply tries to write the token to the file system and reads it back whenever required.

static class TokenCacheHelper
{
/// <summary>
/// Path to the token cache
/// </summary>
public static readonly string CacheFilePath = System.Reflection.Assembly.GetExecutingAssembly().Location + ".msalcache.bin";
private static readonly object FileLock = new object();
public static void BeforeAccessNotification(TokenCacheNotificationArgs args)
{
lock (FileLock)
{
args.TokenCache.DeserializeMsalV3(File.Exists(CacheFilePath)
? ProtectedData.Unprotect(File.ReadAllBytes(CacheFilePath),
null,
DataProtectionScope.CurrentUser)
: null);
}
}
public static void AfterAccessNotification(TokenCacheNotificationArgs args)
{
// if the access operation resulted in a cache update
if (args.HasStateChanged)
{
lock (FileLock)
{
// reflect changesgs in the persistent store
File.WriteAllBytes(CacheFilePath,
ProtectedData.Protect(args.TokenCache.SerializeMsalV3(),
null,
DataProtectionScope.CurrentUser));
}
}
}
internal static void Bind(ITokenCache tokenCache)
{
tokenCache.SetBeforeAccess(BeforeAccessNotification);
tokenCache.SetAfterAccess(AfterAccessNotification);
}
}
view raw TokenCacheHelper.cs hosted with ❤ by GitHub

Sign In/Out Code

The below code snippet shows code for signing in and signing out the user. In addition, there is also code to display the details inside the token.

There is additional code to render the text box and toggling the display of sign in and sign out buttons based on current state of user.

public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private async void SignInButton_Click(object sender, RoutedEventArgs e)
{
string[] scopes = new string[] { "user.read" };
AuthenticationResult authResult = null;
var app = App.PublicClientApp;
try
{
ResultText.Text = "";
authResult = await (app as PublicClientApplication).AcquireTokenInteractive(scopes)
.ExecuteAsync();
DisplayBasicTokenInfo(authResult);
UpdateSignInState(true);
}
catch (Exception ex)
{
ResultText.Text = $"Error Acquiring Token:{Environment.NewLine}{ex}";
}
}
private async void SignOutButton_Click(object sender, RoutedEventArgs e)
{
IEnumerable<IAccount> accounts = await App.PublicClientApp.GetAccountsAsync();
try
{
while (accounts.Any())
{
await App.PublicClientApp.RemoveAsync(accounts.FirstOrDefault());
accounts = await App.PublicClientApp.GetAccountsAsync();
}
UpdateSignInState(false);
}
catch (MsalException ex)
{
ResultText.Text = $"Error signing-out user: {ex.Message}";
}
}
private void UpdateSignInState(bool signedIn)
{
if (signedIn)
{
SignOutButton.Visibility = Visibility.Visible;
SignInButton.Visibility = Visibility.Collapsed;
}
else
{
ResultText.Text = "";
TokenInfoText.Text = "";
SignOutButton.Visibility = Visibility.Collapsed;
SignInButton.Visibility = Visibility.Visible;
}
}
private void DisplayBasicTokenInfo(AuthenticationResult authResult)
{
TokenInfoText.Text = "";
if (authResult != null)
{
TokenInfoText.Text += $"Username: {authResult.Account.Username}" + Environment.NewLine;
TokenInfoText.Text += $"Token Expires: {authResult.ExpiresOn.ToLocalTime()}" + Environment.NewLine;
}
}
}
view raw MainWindow.xaml.cs hosted with ❤ by GitHub

Run and Verify

Now, when you run the application, it will show screen with Login button. If user clicks on the button, the system browser will open and will redirect user to the Azure AD and will show the login screen.

User can enter credentials and after successful login, the MSAL will receive the token from Azure AD as it is listening to the same port. Thus the application will show user details in the text box as shown below.

Below image shows the edge browser and our WPF application behind it.

Wpf App with Azure AD Authentication
Wpf App with Azure AD Authentication

Please note that when you sign out, and sign in again, the browser may show that you are still signed in. This is because, the browser is performing the authentication and keeping the authentication result with it. I could not find any way to clear this from browser. Did you find any way to ?

Let me know your thoughts.

Leave a Reply