Azure AD B2C and MSAL with .NET Core WPF App and Web API

Azure AD B2C and MSAL with .NET Core WPF App and Web API

In this article, let’s have look at how a .NET Core WPF application can be protected using Azure AD B2C and how can it call the APIs which are also protected using the Azure AD B2C.

Prerequisites

For following all steps in this article, we will need Azure Subscription. If you don’t have an Azure subscription, create a free account before you begin.

We will also need instance of Azure AD B2C instance. We will also need API and Angular App already registered in that instance.

Below is the list of those posts which explains all the steps to get there:

You should have Azure AD B2C instance, let’s say samplead.b2clogin.com and user flows B2C_1_SignUpSignIn for signing up or signing in the application.

You also have the the WPF application protected using Azure AD B2C and a separate web API application, which is also protected using Azure AD B2C.

Next, let’s try to make some modifications so that the WPF app can call the protected APIs.

Let’s discuss scopes !

You know that when WPF authenticates the users, it gets an ID Token and an Access Token in the result. The access token should be used to access the protected resources.

The access token can be used to access only those resources, whose scopes were specified while acquiring the tokens, i.e. at the time of logging.

So, you might have understood already that we need to specify additional scopes while sending the sign in request.

But before sending that additional scope, the scope must be added in the app registration corresponding to our WPF app. This step will enable the application to acquire the token which can be used to access the API.

API Permissions for App

So for this, let’s login to Azure Portal, switch to the Azure AD B2C tenant where both these applications are registered. Then search for Azure AD B2C in search box provided on top navigation menu.

Then go to App registrations and open the app registration corresponding to the WPF application and select API Permissions from the left navigation. Next, click on Add a permission button.

It should open a new panel on right side. Then select My APIs tab and select Web API that WPF app should call.

API Permission - Select the API
API Permission – Select the API

Immediately on next screen, select Delegated Permissions and then select the scope and click on Add permissions button.

API Permission Add Scope of the API
API Permission Add Scope of the API

Once this all is done, click on Grant admin consent for samplead button to allow users to use this scope while acquiring the tokens.

Sign in with Additional Scope

In app.xaml.cs, we have specified all the configurations in the form of static variables. So, in those configurations let’s modify three things:

  • Add API Endpoint which WPF App should use
  • Add a new static variable holding the API scope
  • In login scopes, add the API scope too

The complete file should be as shown in below snippet.

public partial class App : Application
{
private static readonly string Tenant = "samplead.onmicrosoft.com";
private static readonly string AzureAdB2CHostname = "samplead.b2clogin.com";
private static readonly string ClientId = "888fff1d-16c3-4de6-92af-3d4ab54a860a";
// Add Api endpoint and Api scope
public static string ApiEndpoint = "https://localhost:44379/weatherforecast";
public static string[] ApiScopes = { "https://samplead.onmicrosoft.com/sample-api/api-scope" };
// These are collective scopes sent with sign in request
public static string[] Scopes = { "openid", "profile", "https://samplead.onmicrosoft.com/sample-api/api-scope" };
private static readonly string RedirectUri = "http://localhost";
public static string PolicySignUpSignIn = "B2C_1_SignUpSignIn";
public static string PolicyEditProfile = "B2C_1_Edit_Profile";
public static string PolicyResetPassword = "B2C_1_Pwd_Reset";
public static string[] Scopes = { "openid", "profile" };
private static string AuthorityBase = $"https://{AzureAdB2CHostname}/tfp/{Tenant}/";
public static string AuthoritySignUpSignIn = $"{AuthorityBase}{PolicySignUpSignIn}";
public static string AuthorityEditProfile = $"{AuthorityBase}{PolicyEditProfile}";
public static string AuthorityResetPassword = $"{AuthorityBase}{PolicyResetPassword}";
public static IPublicClientApplication PublicClientApp { get; private set; }
static App()
{
PublicClientApp = PublicClientApplicationBuilder.Create(ClientId)
.WithB2CAuthority(AuthoritySignUpSignIn)
.WithRedirectUri(RedirectUri)
.WithLogging(Log, LogLevel.Verbose, false) //PiiEnabled set to false
.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

Call API

As API is protected resource, the WPF app will need access token to access it. This access token can be acquired by calling AcquireTokenSilent API. If this API fails for some reason, then this method calls another API – AcquireTokenInteractive – to get the token.

Once the token is available, it is passed in the authorization header to access the API.

Below are the two methods from MainWindow.xaml.cs, which perform the above operations.

/// <summary>
/// Acquires token and then gives call to API
/// </summary>
/// <param name="sender">The sender</param>
/// <param name="e">The event arguments</param>
private async void CallApiButton_Click(object sender, RoutedEventArgs e)
{
//// Try to Acquire token silently
//// If not possible, then try to acquire it interactively.
AuthenticationResult authResult = null;
var app = App.PublicClientApp;
IEnumerable<IAccount> accounts = await App.PublicClientApp.GetAccountsAsync();
try
{
authResult = await app.AcquireTokenSilent(App.ApiScopes, GetAccountByPolicy(accounts, App.PolicySignUpSignIn))
.ExecuteAsync();
}
catch (MsalUiRequiredException ex)
{
// A MsalUiRequiredException happened on AcquireTokenSilentAsync.
// This indicates you need to call AcquireTokenAsync to acquire a token
Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");
try
{
authResult = await app.AcquireTokenInteractive(App.Scopes)
.ExecuteAsync();
}
catch (MsalException msalex)
{
ResultText.Text = $"Error Acquiring Token:{Environment.NewLine}{msalex}";
}
}
catch (Exception ex)
{
ResultText.Text = $"Error Acquiring Token Silently:{Environment.NewLine}{ex}";
return;
}
//// After token is available
//// Call the API and Show the result
if (authResult != null)
{
if (string.IsNullOrEmpty(authResult.AccessToken))
{
ResultText.Text = "Access token is null (could be expired). Please do interactive log-in again.";
}
else
{
ResultText.Text = await GetHttpContentWithToken(App.ApiEndpoint, authResult.AccessToken);
DisplayUserInfo(authResult);
}
}
}
/// <summary>
/// Perform an HTTP GET request to a URL using an HTTP Authorization header
/// </summary>
/// <param name="url">The URL</param>
/// <param name="token">The token</param>
/// <returns>String containing the results of the GET operation</returns>
public async Task<string> GetHttpContentWithToken(string url, string token)
{
var httpClient = new HttpClient();
HttpResponseMessage response;
try
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
response = await httpClient.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
return content;
}
catch (Exception ex)
{
return ex.ToString();
}
}
view raw MainWindow.xaml.cs hosted with ❤ by GitHub

Run and Verify

When you run the application (both API and WPF App), the user can click on Call API or Sign In Buttons. Both will redirect user to Sign In page if user is not signed in yet.

This is because we have handled the MsalUiRequiredException while calling AcquireTokenSilent API. When this exception occurs, the WPF App calls AcquireTokenInteractive API and allowing users to sign in instead of breaking the application.

Once the user is signed in, the user can click on the Call API button (if not done already) which will call API and print the result in the upper text box.

Azure AD B2C - WPF App successfully called Web API
Azure AD B2C – WPF App successfully called Web API

I hope you enjoyed the post. Let me know your thoughts.

Leave a Reply