-
Notifications
You must be signed in to change notification settings - Fork 366
MSAL.NET 3 released
We are excited to announce the release of MSAL.NET 3.0, which has a number of changes, which, we hope, you'll love. Int his article, you'll learn about:
- Why MSAL.NET moved from MSAL 2.x to MSAL 3.x
-
Changes in MSAL 3.x
- reference - list of all changes in MSAL.NET 3
- Plans for deprecation in MSAL.NET 3.x, MSAL 4.x
- Configuring an app got simpler
- Acquiring a token also got simpler
- Advanced: You can provide your own web view
- Breaking changes in MSAL.NET 3.x
- How to maintain SSO with apps written with ADAL v3, ADAL v4, MSAL.NET v2
This paragraph explains why MSAL.NET's major version number was bumped-up from 2 to 3. If you are new to MSAL.NET or are not interested in the reasons for changing the API, you might want to skip to the paragraph which explains the changes in MSAL.NET 3.x.
In August, we released MSAL.NET 2.0, and since you've been using it, and providing feedback. Since august we've released fourteen incremental updates of MSAL.NET, improving both the API and the behavior of the library. You've been awesome helping us moving MSAL.NET to a great authentication library. But you also told us that things could still be improved, that you needed more flexibility. And we felt that we wanted to bring you these improvements, but that we had reached a limit in term of API complexity. Here are the details of this journey to come to the realization that we had to change the paradigm of the API.
Among the GitHub issues, stack overflow questions or direct contact you made with us, it became clear that we had been missing scenarios which were important for you, like letting you pass an HttpClient that MSAL.NET could use. We understand that there are cases where you want fine grain control on the Http proxy for instance, which we had not been able to provide you at all (on .NET core), or on a limited way (.NET framework). Also ASP.NET Core has some very efficient ways of pooling the HttpClient instance, and MSAL.NET clearly did not benefit from it (for details see Use HttpClientFactory to implement resilient HTTP requests)
As some of you start working on writing apps that can not only target users in the Azure public cloud, but also in national and sovereign clouds (for instance the US government cloud), you gave us the feedback that you'd need help making this transition easier. We are several ongoing initiatives for this, but MSAL.NET could already make it easier for you to configure the cloud instance you want to target, and the audience of your application. Along the same lines, we also came to the realization that we could help you with the configuration of application from configuration files, and provide guidance on how to do that better.
It was clear that you needed more configuration options for instantiating applications … therefore more parameters to the constructors, and help to support configuration files.
MSAL.NET enables you to get access tokens to call protected APIs in different ways, depending on your scenario, itself depending on the kind of app you build, and the platform. See Scenarios.
When we implemented the AcquireTokenByDeviceCodeFlow method in MSAL.NET 2.2.0-preview we provided a parameter so that the method is cancelable, and you quickly asked us to do the same for all the AcquireTokenXX methods, which makes sense. This requires adding another parameter to all methods, but we would not want it to be mandatory, and therefore this meant more overloads (see below)
Similarly, the only way to react to conditional access exceptions in MSAL 2.x was, as explained in Handling Claim challenge exceptions in MSAL.NET, to use the extraQueryParameter
. Unfortunately this parameter was available only in two overrides of the AcquireTokenAsync (interactive) flow, and it was not working correctly. We needed to fix it, and provide a proper claims
parameters to AcquireToken methods, including AcquireTokenSilentAsync
Finally, some advanced end-user scenarios like letting the user pre-consent ahead of time were really hard to achieve, as you'll see in the next paragraph.
Let's take one of the scenarios where the developer experience was not good: acquiring a token interactively in a desktop or mobile application (The same reasoning can be extended to other scenarios). In that case you had to call AcquireTokenAsync, but the issue is that this method had already 14 overloads (for details of all the parameters see Acquiring tokens interactively)
But the worse is that to achieve one goal (for instance letting the end user pre-consent to specific scopes ahead of time), you had to use one specific overload with 7 parameters, and, just to pass extraScopesToConsent
, you had to fill-in all the other parameters, and therefore to study the documentation in details to understand what could be good values for these. This pain is illustrated in this article How to get consent for several resources, where you see that you had to write code like this one just to be able to pass-in scopesForVendorApi
(and some of you had no idea about what to use for uiBehavior, or authority)
var result = await app.AcquireTokenAsync(scopesForCustomerApi,
accounts.FirstOrDefault(),
uiBehavior,
string.Empty,
scopesForVendorApi,
app.Authority,
uiParent);
There are several examples of this pain (extra query parameters being another ), and we knew that we had to add more parameters to the AcquireTokenXXXAsync methods (for instance the claims to handle conditional access), and therefore it seemed that we had to add more overloads.
But then came a question: would you think that having 56 overloads for AcquireTokenAsync would have been reasonable?
Wouldn't you prefer to only set the parameters you need? for instance use another syntax like the following?
var result = await app.AcquireTokenInteractive(scopesForCustomerApi)
.WithAccount(accounts.FirstOrDefault())
.WithExtraScopesToConsent(scopesForVendorApi)
.ExecuteAsync();
We think that a limit was reached in term of overloads, both on some AcquireTokenXXX methods, and on the constructors for the applications. We were going to proposal another approach, based on fluent APIs.
Another request you made was to give you more flexibility. For instance you wanted to Allow public applications to acquire token via authorization code #863: In .NET Core we don't provide interactive authentication because no Web control is available there (yet). But there are cases, like what the Azure CLI team have done, where you want to let the user sign-in and consent into the machine browser on the desktop. This can be done, but at the same time you don't want to sacrifice to security. Therefore you asked us to provide an extensible way to let you do that. Another example of this is our own Visual Studio team who has Electron applications (VS Feedback and Azure Storage explorer) and wanted sign-in to be delegated in their UI, while still benefiting from the rest of MSAL.NET.
Finally we heard that we could improve the naming of things a bit. Here is an example UIBehavior
was the mechanism, in MSAL 2.x by which you could customize the user experience and direct the Microsoft identity platform to present a particular prompt experience. We got the feedback that the name UIBehavior
did not speak much, and everybody in the industry is naming it Prompt
therefore we've decided to rename UIBehavior
to Prompt
in all MSAL libraries, starting with MSAL 3.x
Work was done between MSAL 1.x and MSAL 2.0 to enable sharing token caches between ADAL and MSAL on the platforms supported by ADAL, and also between MSAL libraries on the platforms. However as we were working on initiatives to enable SSO between tools written in different languages, we came to the realization that, on Windows/linux/MacOS, MSAL.NET and MSAL.Python (for instance), although using the same cache schema, did not share the same layout for the blob that you can serialize. Therefore we slightly changed the layout format of the token cache blob to harmonize the file format of MSAL.NET, and MSAL Python and MSAL.Java. This is a breaking change, but we also provided a migration path.
To summarize what we learnt: you needed:
- more flexibility in configuring your apps,
- more options to acquire tokens
We had to change the API to enable this flexibility without making it overly complex. Therefore we've decided that MSAL.NET would take a few breaking changes.
Breaking changes in MSAL.NET 3:
-
UIBehavior
was renamed toPrompt
(breaking change) -
TokenCacheNotificationArgs
now surfaces anITokenCache
instead of aTokenCache
. This will allow MSAL.NET to provide, in the future, various token cache implementations. -
TokenCacheExtensions
was removed and its methods moved toITokenCache
(this is a binary breaking change, but not a source level breaking change)
Changes related to improving app Creation and configuration
- New class
ApplicationOptions
help you build an application, for instance from a configuration file - New interface
IMsalHttpClientFactory
to pass-in the HttpClient to use by MSAL.NET to communicate with the endpoints of Microsoft identity platform for developers. - New classes
PublicClientApplicationBuilder
andConfidentialClientApplicationBuilder
propose a fluent API to instantiate respectively classes implementingIPublicClientApplication
andIConfidentialClientApplication
including from configuration files, but also setting per application logging and telemetry, and setting the HttpClient. - New delegates
TelemetryCallback
andTokenCacheCallback
can be set at application construction - New enumerations
AadAuthorityAudience
andAzureCloudInstance
help you writing applications for sovereign and national clouds, and help you choose the audience for your application.
Changes related to improving token acquisition:
-
ClientApplicationBase
now implementsIClientApplicationBase
and has new members:-
AppConfig
of new typeIAppConfig
contains the configuration of the application -
UserTokenCache
of new typeITokenCache
contains the user token cache (for both public and confidential client applications for all flows, butAcquireTokenForClient
)- New fluent API
AcquireTokenSilent
- New fluent API
-
-
PublicClientApplication
andIPublicClientApplication
have four new fluent APIs:AcquireTokenByIntegratedWindowsAuth
,AcquireTokenByUsernamePassword
,AcquireTokenInteractive
,AcquireTokenWithDeviceCode
. -
ConfidentialClientApplication
has new members:-
AppTokenCache
used byAcquireTokenForClient
- five new fluent APIs:
AcquireTokenByAuthorizationCode
,AcquireTokenForClient
,AcquireTokenOnBehalfOf
,GetAuthorizationRequestUrl
,IByRefreshToken.AcquireTokenByRefreshToken
-
- New extensibility mechanism to enable public client applications to provide, in a secure way, their own browsing experience to let the user interact with the Microsoft identity platform endpoint (advanced). For this, applications need to implement the
ICustomWebUi
interface and throwMsalCustomWebUiFailedException
exceptions in case of failure. This can be useful in the case of platforms which don't have yet a Web browser. For instance the Visual Studio Feedback tool is an Electron application which uses this mechanism. -
MsalServiceException
now surfaces two new properties:-
CorrelationId
which can be useful when you interact with Microsoft support. -
SubError
which indicates more details about why the error happened, including hints on how to communicate with the end user.
-
Changes related to the token cache:
- New interface
ITokenCache
contains primitives to serialize and deserialize the token cache and set the delegates to react to cache changes
As seen above, we are proposing a new MSAL.NET V3.0 API based on builders. For the moment (MSAL 3.0), we propose this new API side by side with the previous V2.0 APIs (having AcquireTokenXXXAsync, and many overrides of these), in order to leave you time to migrate to the new API, here is the plan that we propose
Here is the current proposal, on which we'd like to get your feedback:
- In 3 months, supposing that, like us, you love the new APIs, we'd want to deprecate the old V2.0 API. At that point, if you still use the old style API, you'll see warnings encouraging you to move to the new API.
- Then in the next major version of MSAL.NET, the old V2.0 style APIs would disappear, leaving a very simple shape for the API. In the class diagram below, we've hidden the V2.0 style APIs, and that gives you an idea of what the public API could be in the next major release.
With MSAL.NET 3.x, we made it much simpler to configure your application. You don't need to choose an override for a constructor. The recommended way is to use the static PublicClientApplicationBuilder and ConfidentiaClientApplicationBuilder classes and call the Create or CreateFromOption() method. They both return a builder, to which you chain optional properties. When you have all your properties, you call Build() and that's it!
If you want to create a desktop application you can do it with the minimum information.
IPublicClientApplication app;
app = PublicClientApplicationBuilder.Create(clientId)
.Build();
By default this application will target Work and School Accounts and Microsoft personal accounts from the Microsoft Azure public cloud.
Now let's assume that your application is a line of business application which is only for your organization, then you can write:
IPublicClientApplication app;
app = PublicClientApplicationBuilder.Create(clientId)
.WithAuthority(AzureCloudInstance.AzurePublic, tenantId)
.Build();
Where it becomes interesting is that programming for national clouds has now become simple: if you want your application to be a multi-tenant application in a national or sovereign cloud, you could write for instance, write:
IPublicClientApplication app;
app = PublicClientApplicationBuilder.Create(clientId)
.WithAuthority(AzureCloudInstance.AzureUsGovernment, AadAuthorityAudience.AzureAdMultipleOrgs)
.Build();
Finally, if you are an Azure AD B2C developer, you can specify your tenant like this. Note that you don't need to say that you want to bypass authority validation, which, we heared was scary.
IPublicClientApplication app;
app = PublicClientApplicationBuilder.Create(clientId)
.WithB2CAuthority($"https://fabrikamb2c.b2clogin.com/tfp/{tenant}/{PolicySignInSignUp}")
.Build();
Of course there are several overloads of .WithAuthority with many different cases. For more information see Client applications in MSAL 3.x
Now, chances are that you probably have the application configuration in a file, rather than in the code. This is also possible as the application builder can create an application from Options. Here is how:
PublicClientApplicationOptions = GetOptions(); // your own method.
var app = PublicClientApplicationBuilder.CreateWithApplicationOptions(options)
.Build();
You'll find a more complete code sample in Client applications in MSAL 3.x
Once you have your application, you want to acquire tokens. The same kind of pattern was used where you call:
app.AcquireTokenXXX(scopes, mandatory-parameters)
.WithOptionalProperty(optional-property).
.ExecuteAsync();
This means you no longer have plenty of overrides, you can choose the parameters you want to set. Finally ExecuteAsync() had an override taking a CancellationToken
argument, making all the AcquireTokenXX methods cancellable, as you requested!
app.AcquireTokenXXX(scopes, mandatory-parameters)
.WithOptionalProperty(optional-property).
.ExecuteAsync(cancellationToken);
As mentionned in the Why did MSAL move from 2.x to 3.x? paragraph, as we were cleaning-up the public API, and simplifying it, we introduced a small number of breaking changes which, we'd expect, should not affect you too much for most of the scenarios if you use the MSAL 2.x type API.
- We renamed a type (UIBehavior)
- Some properties of IClientApplicationBase and ClientApplicationBase did not make sense any longer
- the application builder now create the token cache for you, we recommand you to use this mechanism, not instantiate
TokenCache
yourself
The UIBehavior
class was used until MSAL.NET 3.x to specify, in AcquireTokenAsync
(interactive) which prompt the application developer wanted to expose to the user. The class was renamed to Prompt
.
The recommendation of the .NET team is to avoid using secure strings. They are not that secure (not implemented at the OS level) and are difficult to use in managed code. If you are interested in details, look at this article: DE0001: SecureString shouldn't be used
Therefore we changed the signature of AcquireTokenByUsernamePasswordAsync
so that the password is now a plain string.
AcquireTokenByUsernamePasswordAsync(IEnumerable<string> scopes,
string username,
string password)
Until MSAL.NET 3.0, you used to set the logger
public interface ITokenCache
{
void Deserialize(byte[] unifiedState); // MSAL V2.0 format
void DeserializeUnifiedAndAdalCache(CacheData cacheData); // ADAL V3.0 and MSAL V3.0 format
void DeserializeV3(byte[] bytes); // MSAL V3.0 unified cache format
byte[] Serialize(); // MSAL V2.0 format
CacheData SerializeUnifiedAndAdalCache(); // ADAL V3.0 and MSAL V3.0 format
byte[] SerializeV3(); // MSAL V3.0 unified cache format
void SetAfterAccess(TokenCacheCallback afterAccess);
void SetBeforeAccess(TokenCacheCallback beforeAccess);
void SetBeforeWrite(TokenCacheCallback beforeWrite);
}
Some properties in the V2.0 API style were settable after the application construction. The V3 builder style makes the application properties immutable. From MSAL.NET 3.x, if you need to use these properties, you now need to:
- Set them when you are building the application (that is with .With clauses on the application builder)
- Read them from the application's configuration
Here is the detail. They all are held by:
Property | Description | Which method of the application builder to user | How to read it? |
---|---|---|---|
Component | Identifier of the component (libraries/SDK) consuming MSAL.NET, allowing for disambiguation between MSAL usage by the app vs MSAL usage by component libraries (mostly for telemetry purpose) | .WithComponent(component) |
app.AppConfig.Component |
SliceParameters | Used to be a set of custom query parameters that may be sent to the STS for dogfood testing or debugging as a string of segments of the form key=value separated by an ampersand character |
.WithExtraQueryParameters |
|
ValidateAuthority | No longer needed | .WithAADAuthority does not require it, .WithB2CAuthority either, .WithAuthority has a Boolean for specific cases | |
RedirectUri | The redirect URI (also known as Reply URI or Reply URL), is the URI at which Azure AD will contact back the application with the tokens | Set to defaults, or, for client applications, in the constructor. | .WithRedirectUri |
- Home
- Why use MSAL.NET
- Is MSAL.NET right for me
- Scenarios
- Register your app with AAD
- Client applications
- Acquiring tokens
- MSAL samples
- Known Issues
- Acquiring a token for the app
- Acquiring a token on behalf of a user in Web APIs
- Acquiring a token by authorization code in Web Apps
- AcquireTokenInteractive
- WAM - the Windows broker
- .NET Core
- Maui Docs
- Custom Browser
- Applying an AAD B2C policy
- Integrated Windows Authentication for domain or AAD joined machines
- Username / Password
- Device Code Flow for devices without a Web browser
- ADFS support
- High Availability
- Regional
- Token cache serialization
- Logging
- Exceptions in MSAL
- Provide your own Httpclient and proxy
- Extensibility Points
- Clearing the cache
- Client Credentials Multi-Tenant guidance
- Performance perspectives
- Differences between ADAL.NET and MSAL.NET Apps
- PowerShell support
- Testing apps that use MSAL
- Experimental Features
- Proof of Possession (PoP) tokens
- Using in Azure functions
- Extract info from WWW-Authenticate headers
- SPA Authorization Code