ADAL to MSAL migration for the Azure AD B2C “B2CGraphClient” utility

Rory Braybrook
The new control plane
4 min readApr 13, 2021
Birds by Laymik from the Noun Project

As most of you know, ADAL is being deprecated.

“Starting June 30th, 2020, we will no longer add new features to ADAL. We’ll continue adding critical security fixes to ADAL until June 30th, 2022. After this date, your apps using ADAL will continue to work, but we recommend upgrading to MSAL to take advantage of the latest features and to stay secure.

Note that your existing apps will continue working without modification. If you’re planning to keep them beyond June 30th, 2022, you should consider updating your apps to MSAL to keep them secure, but migrating to MSAL isn’t required to maintain existing functionality”.

There is a very useful utility running on .NET but it uses the ADAL library and (despite the above), I have battled to get it to work on some newer B2C tenants. Note that the link to the documentation in that repo. points to a newer version of that utility running on .NET Core and that utility does not have the same functionality. In particular, you can’t do CRUD operations on extension attributes. For some reason, it uses hard coded extension attributes:

const string customAttributeName1 = "FavouriteSeason";
const string customAttributeName2 = "LovesPets";

BTW, these are the newer version commands:

Command Description
====================
[1] Get all users (one page)
[2] Get user by object ID
[3] Get user by sign-in name
[4] Delete user by object ID
[5] Update user password
[6] Create users (bulk import)
[7] Create user with custom attributes and show result
[8] Get all users (one page) with custom attributes
[help] Show available commands
[exit] Exit the program
— — — — — — — — — — — — -

The older version has these commands:

Get-User : Read users from your B2C directory. Optionally accepts an ObjectId as a 2nd argument, and query expression as a 3rd argument.
Create-User : Create a new user in your B2C directory. Requires a path to a .json file which contains required and optional information as a 2nd argument.
Update-User : Update an existing user in your B2C directory. Requires an objectId as a 2nd argument & a path to a .json file as a 3rd argument.
Delete-User : Delete an existing user in your B2C directory. Requires an objectId as a 2nd argument.
Get-Extension-Attribute : Lists all extension attributes in your B2C directory. Requires the b2c-extensions-app objectId as the 2nd argument.
Get-B2C-Application : Get the B2C Extensions Application in your B2C directory, so you can retrieve the objectId and pass it to other commands.
Help : Prints this help menu.
Syntax : Gives syntax information for each command, along with examples.

The other change is that ADAL uses the Azure AD Graph API whereas MSAL uses the Microsoft Graph API.

Given the problems I had with the older version on newer B2C tenants, I decided to port the project from ADAL to MSAL but still using the .NET framework.

There is no tool that does the port for you so it has to be done manually.

There is some good documentation on this process.

This is a console (daemon) application using the client credential flow so I followed this guidance:

Based on these, there is an interface for this in MSAL — the IConfidentialClientApplication interface:

using Microsoft.Identity.Client;IConfidentialClientApplication app;app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
.WithClientSecret(config.ClientSecret)
.WithAuthority(new Uri(config.Authority))
.Build();

You know need to use scopes:

ResourceId = "someAppIDURI";
var scopes = new [] { ResourceId+"/.default"};

You get the JWT:

using Microsoft.Identity.Client;// With client credentials flows, the scope is always of the shape "resource/.default" because the
// application permissions need to be set statically (in the portal or by PowerShell), and then granted by
// a tenant administrator.
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
AuthenticationResult result = null;
try
{
result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
}
catch (MsalUiRequiredException ex)
{
// The application doesn't have sufficient permissions.
// - Did you declare enough app permissions during app creation?
// - Did the tenant admin grant permissions to the application?
}
catch (MsalServiceException ex) when (ex.Message.Contains("AADSTS70011"))
{
// Invalid scope. The scope has to be in the form "https://resourceurl/.default"
// Mitigation: Change the scope to be as expected.
}

Notice the specific exceptions.

Then pass the JWT to the API.

httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
// Call the web API.
HttpResponseMessage response = await _httpClient.GetAsync(apiUri);
...
}

The Github repo. is here.

Hope it helps 😃

All good!

--

--

Rory Braybrook
The new control plane

NZ Microsoft Identity dude and MVP. Azure AD/B2C/ADFS/Auth0/identityserver. StackOverflow: https://bit.ly/2XU4yvJ Presentations: http://bit.ly/334ZPt5