Migrate confidential client applications from ADAL.NET to MSAL.NET

In this how-to guide you'll migrate a confidential client application from Azure Active Directory Authentication Library for .NET (ADAL.NET) to Microsoft Authentication Library for .NET (MSAL.NET). Confidential client applications include web apps, web APIs, and daemon applications that call another service on their own behalf. For more information about confidential apps, see Authentication flows and application scenarios. If your app is based on ASP.NET Core, see Microsoft.Identity.Web.

For app registrations:

  • You don't need to create a new app registration. (You keep the same client ID.)
  • You don't need to change the preauthorizations (admin-consented API permissions).

Migration steps

  1. Find the code that uses ADAL.NET in your app.

    The code that uses ADAL in a confidential client app instantiates AuthenticationContext and calls either AcquireTokenByAuthorizationCode or one override of AcquireTokenAsync with the following parameters:

    • A resourceId string. This variable is the app ID URI of the web API that you want to call.
    • An instance of IClientAssertionCertificate or ClientAssertion. This instance provides the client credentials for your app to prove the identity of your app.
  2. After you've identified that you have apps that are using ADAL.NET, install the MSAL.NET NuGet package Microsoft.Identity.Client and update your project library references. For more information, see Install a NuGet package. To use token cache serializers, install Microsoft.Identity.Web.TokenCache.

  3. Update the code according to the confidential client scenario. Some steps are common and apply across all the confidential client scenarios. Other steps are unique to each scenario.

    Confidential client scenarios:

You might have provided a wrapper around ADAL.NET to handle certificates and caching. This guide uses the same approach to illustrate the process of migrating from ADAL.NET to MSAL.NET. However, this code is only for demonstration purposes. Don't copy/paste these wrappers or integrate them in your code as they are.

Migrate daemon apps

Daemon scenarios use the OAuth2.0 client credential flow. They're also called service-to-service calls. Your app acquires a token on its own behalf, not on behalf of a user.

Find out if your code uses daemon scenarios

The ADAL code for your app uses daemon scenarios if it contains a call to AuthenticationContext.AcquireTokenAsync with the following parameters:

  • A resource (app ID URI) as a first parameter
  • IClientAssertionCertificate or ClientAssertion as the second parameter

AuthenticationContext.AcquireTokenAsync doesn't have a parameter of type UserAssertion. If it does, then your app is a web API, and it uses the web API calling downstream web APIs scenario.

Update the code of daemon scenarios

The following steps for updating code apply across all the confidential client scenarios:

  1. Add the MSAL.NET namespace in your source code: using Microsoft.Identity.Client;.
  2. Instead of instantiating AuthenticationContext, use ConfidentialClientApplicationBuilder.Create to instantiate IConfidentialClientApplication.
  3. Instead of the resourceId string, MSAL.NET uses scopes. Because applications that use ADAL.NET are preauthorized, you can always use the following scopes: new string[] { $"{resourceId}/.default" }.
  4. Replace the call to AuthenticationContext.AcquireTokenAsync with a call to IConfidentialClientApplication.AcquireTokenXXX, where XXX depends on your scenario.

In this case, replace the call to AuthenticationContext.AcquireTokenAsync with a call to IConfidentialClientApplication.AcquireTokenClient.

Here's a comparison of ADAL.NET and MSAL.NET code for daemon scenarios:

ADAL

MSAL

using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;

public partial class AuthWrapper
{
const string ClientId = "Guid (AppID)";
const string authority 
= "https://login.microsoftonline.com/{tenant}";
// App ID URI of web API to call
const string resourceId = "https://target-api.domain.com";
X509Certificate2 certificate = LoadCertificate();



public async Task<AuthenticationResult> GetAuthenticationResult()
{


var authContext = new AuthenticationContext(authority);
var clientAssertionCert = new ClientAssertionCertificate(
ClientId,
certificate);


var authResult = await authContext.AcquireTokenAsync(
resourceId,
clientAssertionCert,
);

return authResult;
}
}
using Microsoft.Identity.Client;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;

public partial class AuthWrapper
{
const string ClientId = "Guid (Application ID)";
const string authority 
= "https://login.microsoftonline.com/{tenant}";
// App ID URI of web API to call
const string resourceId = "https://target-api.domain.com";
X509Certificate2 certificate = LoadCertificate();

IConfidentialClientApplication app;

public async Task<AuthenticationResult> GetAuthenticationResult()
{

var app = ConfidentialClientApplicationBuilder.Create(ClientId)
.WithCertificate(certificate)
.WithAuthority(authority)
.Build();

// Setup token caching https://learn.microsoft.com/azure/active-directory/develop/msal-net-token-cache-serialization?tabs=aspnet
// For example, for an in-memory cache with 1GB limit, use  
app.AddInMemoryTokenCache(services =>
{
// Configure the memory cache options
services.Configure<MemoryCacheOptions>(options =>
{
options.SizeLimit = 1024 * 1024 * 1024; // in bytes (1 GB of memory)
});
}

var authResult = await app.AcquireTokenForClient(
new [] { $"{resourceId}/.default" })
// .WithTenantId(specificTenant)
// See https://aka.ms/msal.net/withTenantId
.ExecuteAsync()
.ConfigureAwait(false);

return authResult;
}
}

Benefit from token caching

If you don't setup token caching, the token issuer will throttle you, resulting in errors. It also takes a lot less to get a token from the cache (10-20ms) than it is from ESTS (500-30000ms).

If you want to implement a distributed token cache, see Token cache for a web app or web API (confidential client application) and the sample active-directory-dotnet-v1-to-v2/ConfidentialClientTokenCache.

Learn more about the daemon scenario and how it's implemented with MSAL.NET or Microsoft.Identity.Web in new applications.

MSAL benefits

Key benefits of MSAL.NET for your app include:

  • Resilience. MSAL.NET helps make your app resilient through:

    • Microsoft Entra ID Cached Credential Service (CCS) benefits. CCS operates as a Microsoft Entra backup.
    • Proactive renewal of tokens if the API that you call enables long-lived tokens through continuous access evaluation.
  • Security. You can acquire Proof of Possession (PoP) tokens if the web API that you want to call requires it. For details, see Proof Of Possession tokens in MSAL.NET

  • Performance and scalability. If you don't need to share your cache with ADAL.NET, disable the legacy cache compatibility when you're creating the confidential client application (.WithLegacyCacheCompatibility(false)) to significantly increase performance.

    app = ConfidentialClientApplicationBuilder.Create(ClientId)
            .WithCertificate(certificate)
            .WithAuthority(authority)
            .WithLegacyCacheCompatibility(false)
            .Build();
    

Troubleshooting

MsalServiceException

The following troubleshooting information makes two assumptions:

  • Your ADAL.NET code was working.
  • You migrated to MSAL by keeping the same client ID.

If you get an exception with either of the following messages:

AADSTS700027: Client assertion contains an invalid signature. [Reason - The key was not found.]

AADSTS90002: Tenant 'cf61953b-e41a-46b3-b500-663d279ea744' not found. This may happen if there are no active subscriptions for the tenant. Check to make sure you have the correct tenant ID. Check with your subscription administrator.

Troubleshoot the exception using these steps:

  1. Confirm that you're using the latest version of MSAL.NET.
  2. Confirm that the authority host that you set when building the confidential client app and the authority host that you used with ADAL are similar. In particular, is it the same cloud (Azure Government, Microsoft Azure operated by 21Vianet, or Azure Germany)?

MsalClientException

In multi-tenant apps, specify a common authority when building the app to target a specific tenant such as, the tenant of the user when calling a web API. Since MSAL.NET 4.37.0, when you specify .WithAzureRegion at the app creation, you can no longer specify the Authority using .WithAuthority during the token requests. If you do, you'll get the following error when updating from previous versions of MSAL.NET:

MsalClientException - "You configured WithAuthority at the request level, and also WithAzureRegion. This is not supported when the environment changes from application to request. Use WithTenantId at the request level instead."

To remediate this issue, replace .WithAuthority on the AcquireTokenXXX expression by .WithTenantId. Specify the tenant using either a GUID or a domain name.

Next steps

Learn more about: