Skip to content

msal net 4.1

Jean-Marc Prieur edited this page Jun 27, 2019 · 35 revisions

MSAL.NET 4.1 to be released

We are excited to release an incremental update of MSAL.NET addressing some of the issues you raised, and bringing:

  • Improved options to control the way the System Web browser will handle the communication with the user in case of success or failure
  • A new property named Classification on MsalUiRequiredException to help you providing an optimal user experience when you receive an invalid grant error.
  • Support for client assertions
  • Bug fixes
  • (maybe more features? improved CA? Client assertions? DefaultRedirectURI?

Improved experience with the system web browser (on .NET Core)

From MSAL.NET 4.0.0, you have been able to use the interactive token acquisition with .NET Core, by delegating the sign-in and consent part to the system Web browser on your machine. MSAL.NET 4.1, brings improvements to this experience by helping you run a specific browser if you wish, and by giving you ways to decide what to display to the user in case of a successful authentication, and in case of failure

MSAL.NET 4.1 adds a new class named SystemWebViewOptions which enables you to specify:

  • the URI to navigate to (BrowserRedirectError), or the HTML fragment to display (HtmlMessageError) in case of sign-in / consent errors in the System web browser
  • the URI to navivate to (BrowserRedirectSuccess), or the HTML fragment to display (HtmlMessageSuccess) in case of successful sign-in / consent.
  • the action to run to start the system browser. For this you cnan provide your own implementation by setting the OpenBrowserAsync delegate. The class also provides a default implementation for two browsers: OpenWithEdgeBrowserAsync and OpenWithChromeEdgeBrowserAsync, respectively for Microsoft Edge and Edge on Chromium.
public class SystemWebViewOptions
{
 public SystemWebViewOptions();
 public Uri BrowserRedirectError { get; set; }
 public Uri BrowserRedirectSuccess { get; set; }
 public string HtmlMessageError { get; set; }
 public string HtmlMessageSuccess { get; set; }
 public Func<Uri, Task> OpenBrowserAsync { get; set; }
 public static Task OpenWithChromeEdgeBrowserAsync(Uri uri);
 public static Task OpenWithEdgeBrowserAsync(Uri uri);
}

To use this structure you can write something like the following:

IPublicClientApplication app;
...

options = new SystemWebViewOptions
{
 HtmlMessageError = "<b>Sign-in failed. You can close this tab ...</b>",
 BrowserRedirectSuccess = "https://contoso.com/help-for-my-awesome-commandline-tool.html"
};

var result = app.AcquireTokenInteractive(scopes)
                .WithEmbeddedWebView(false)       // The default in .NET Core
                .WithSystemWebViewOptions(options)
                .Build();

New Classification property on MsalUiRequiredException enables you to provide a better User experience in your apps

What was the problem?

One of common status codes returned from MSAL.NET when calling AcquireTokenSilent() in MsalError.InvalidGrantError. This status code means that the application should call the authentication library again, but in interactive mode (AcquireTokenInteractive or AcquireTokenByDeviceCodeFlow for public client applications, and do a challenge in Web apps). This is because additional user interaction is required before authentication token can be issued.

The interaction aims at having the user do an action. Some of those conditions are easy for users to resolve (e.g. accept Terms of Use with a single click), and some cannot be resolved with the current configuration (e.g. the machine in question needs to connect to a specific corporate network). Some help the user setting-up Multi-factor authentication, or install authenticator on their device.

Depending on how complicated and involved required user interaction is, apps may want to show different user experience for different levels of difficulty of user interaction. For example, if the app is trying to show multiple resources to the user at the same time (e.g. items in a collection returned by a search result), the app may choose to not display specific results for which authentication flow to resolve InvalidGrant condition is too intrusive, but may choose to enable resolution of InvalidGrant condition if resolution is quick and simple. In the same idea, they might choose to batch the resolution.

Until MSAL.NET 4.1, applications did not have a way to distinguish between different classes of conditions that cause InvalidGrant, and therefore could only have a very generic way to handle this state, which leads to end user confusion and user experience dead ends in some of application flows.

For full details see Issue #1148

The Classification property

Classification Meaning Recommended handling
basic_action Condition can be resolved by user interaction during the interactive authentication flow. Call AcquireTokenInteractively().
additional_action Condition can be resolved by additional remedial interaction with the system, outside of the interactive authentication flow. Call AcquireTokenInteractively() to show a message that explains the remedial action. Calling application may choose to hide flows that require additional_action if the user is unlikely to complete the remedial action.
message_only Condition cannot be resolved at this time. Launching interactive authentication flow will show a message explaining the condition. Call AcquireTokenInteractively() to show a message that explains the condition. AcquireTokenInteractively() will return UserCanceled error after the user reads the message and closes the window. Calling application may choose to hide flows that result in message_only if the user is unlikely to benefit from the message.
consent_required User consent is missing, or has been revoked. Call AcquireTokenInteractively() for user to give consent.
user_password_expired User's password has expired. Call AcquireTokenInteractively() so that user can reset their password.
[empty string] Condition may be resolved by user interaction during the interactive authentication flow. Call AcquireTokenInteractively().

Code snippet using the Classification property

AuthenticationResult res;
try
{
 res = await application.AcquireTokenSilent(scopes, account)
                        .ExecuteAsync();
}
catch (MsalUiRequiredException ex) when (ex.ErrorCode == MsalError.InvalidGrantError)
{
switch (ex.Classification)
{
 case MsalUiRequiredException.MessageOnly:
 // You might want to call AcquireTokenInteractive() to show a message that explains the condition. 
 // AcquireTokenInteractive() will return UserCanceled error after the user reads the message 
 // and closes the window. 
 // Calling application may choose to hide flows that result in message_only if the user is unlikely to benefit from the message
 try
 {
  res = await application.AcquireTokenInteractive(scopes)
                         .ExecuteAsync();
 }
 catch (MsalClientException ex2) when (ex2.ErrorCode == MsalError.AuthenticationCanceledError)
 {
  // Do nothing. The user has seen the message
 }
 break;

 case MsalUiRequiredException.AdditionalAction:
 // You might want to call AcquireTokenInteractive() to show a message that explains the remedial action. 
 // The calling application may choose to hide flows that require additional_action if the user 
 // is unlikely to complete the remedial action (even if this means a degraded experience)

 case MsalUiRequiredException.BasicAction:
 // Call AcquireTokenInteractive()

 case MsalUiRequiredException.ConsentRequired:
 // Call AcquireTokenInteractive() for user to give consent.

 case MsalUiRequiredException.UserPasswordExpired:
 // Call AcquireTokenInteractive() so that user can reset their password

 case "":
 // Condition may be resolved by user interaction during the interactive authentication flow.

 res = await application.AcquireTokenInteractive(scopes)
                                                .ExecuteAsync();
 break;

 default:
 break;
}

Confidential client applications now support client assertions

New capabilities for confidential client applications to prove their identity

In order to prove their identity, confidential client applications exchange a secret with Azure AD. This can be a:

  • a client secret (application password),
  • a certificate, which is really used to build a signed assertion containing standard claims. This can also be a signed assertion directly.

MSAL.NET 4.1 adds a new capabilities for this advanced scenario: in addition to .WithClientSecret() and .WithCertificate(), it now provides three new methods: .WithSignedAssertion(), .WithClientClaims() and .WithClientAdditionalClaims()

WithSignedAssertion

The first method takes a signed client assertion. It's expected to be a Base64 encoding of a JWT token which needs to contain mandatory claims. To use it:

string signedClientAssertion = ComputeAssertion();
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
                                          .WithClientAssertion(signedClientAssertion)
                                          .Build();

The claims expected by Azure AD are:

Claim type Value Description
aud https://login.microsoftonline.com/{tenantId}/v2.0
The "aud" (audience) claim identifies the recipients that the JWT is intended for (here Azure AD) See [RFC 7519, Section 4.1.3]
exp Thu Jun 27 2019 15:04:17 GMT+0200 (Romance Daylight Time) The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. See [RFC 7519, Section 4.1.4]
iss {ClientID} The "iss" (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The "iss" value is a case-sensitive string containing a StringOrURI value. [RFC 7519, Section 4.1.1]
jti (a Guid) The "jti" (JWT ID) claim provides a unique identifier for the JWT. The identifier value MUST be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different data object; if the application uses multiple issuers, collisions MUST be prevented among values produced by different issuers as well. The "jti" claim can be used to prevent the JWT from being replayed. The "jti" value is a case-sensitive string. [RFC 7519, Section 4.1.7]
nbf Thu Jun 27 2019 14:54:17 GMT+0200 (Romance Daylight Time) The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. [RFC 7519, Section 4.1.5]
sub {ClientID} The "sub" (subject) claim identifies the subject of the JWT. The claims in a JWT are normally statements about the subject. The subject value MUST either be scoped to be locally unique in the context of the issuer or be globally unique. The See [RFC 7519, Section 4.1.2]

WithAdditionalClientClaims

string ipAddress = "192.168.1.2";
X509Certificate2 certificate = ReadCertificate(config.CertificateName);
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
                                          .WithAuthority(new Uri(config.Authority))
                                          .WithAdditionalClientClaims(certificate, 
                                                                      new Dictionary<string, string> { { "client_ip", ipAddress } })
                                          .Build();

WithClientClaims

string signedClientAssertion = ComputeAssertion();
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
                                          .WithClientAssertion(signedClientAssertion)
                                          .Build();

Bug fixes

  • When using the ConfidentialClientApplicationOptions and including, for example Instance = "https://login.microsoftonline.com/", MSAL.NET was concatenating the double-slash. MSAL.NET will now check for a trailing slash and remove it. There is no action needed on the part of the developer. See issue 1196 for details.
  • When using ADFS 2019, if no login-hint was included in the call, a null ref was thrown. See issue 1214 for details
  • For certain older auth libraries, there is an issue with null handlnig in json. The json serializer in MSAL.NET no longer writes values to json for which the values are null, this is especially important for foci_id. See issue 1189 and [issue https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/1176)

Getting started with MSAL.NET

Acquiring tokens

Desktop/Mobile apps

Web Apps / Web APIs / daemon apps

Advanced topics

News

FAQ

Other resources

Clone this wiki locally