Handle MSAL exceptions and errors

This article gives an overview of the different types of errors and recommendations for handling common sign-in errors.

MSAL error handling basics

Exceptions in Microsoft Authentication Library (MSAL) are intended for app developers to troubleshoot--not for displaying to end users. Exception messages are not localized.

When processing exceptions and errors, you can use the exception type itself and the error code to distinguish between exceptions. For a list of error codes, see Authentication and authorization error codes.

During the sign-in experience, you may encounter errors about consents, Conditional Access (MFA, Device Management, Location-based restrictions), token issuance and redemption, and user properties.

See the following section that matches the language you are using for more details about error handling for your app.

When processing .NET exceptions, you can use the exception type itself and the ErrorCode member to distinguish between exceptions. ErrorCode values are constants of type MsalError.

You can also have a look at the fields of MsalClientException, MsalServiceException, and MsalUIRequiredException.

If MsalServiceException is thrown, try Authentication and authorization error codes to see if the code is listed there.

Common .NET exceptions

Here are the common exceptions that might be thrown and some possible mitigations:

Exception Error code Mitigation
MsalUiRequiredException AADSTS65001: The user or administrator has not consented to use the application with ID '{appId}' named '{appName}'. Send an interactive authorization request for this user and resource. You need to get user consent first. If you aren't using .NET Core (which doesn't have any Web UI), call (once only) AcquireTokeninteractive. If you are using .NET core or don't want to do an AcquireTokenInteractive, the user can navigate to a URL to give consent: https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id={clientId}&response_type=code&scope=user.read. to call AcquireTokenInteractive: app.AcquireTokenInteractive(scopes).WithAccount(account).WithClaims(ex.Claims).ExecuteAsync();
MsalUiRequiredException AADSTS50079: The user is required to use multi-factor authentication (MFA). There is no mitigation. If MFA is configured for your tenant and Azure Active Directory (AAD) decides to enforce it, you need to fall back to an interactive flow such as AcquireTokenInteractive or AcquireTokenByDeviceCode.
MsalServiceException AADSTS90010: The grant type isn't supported over the /common or /consumers endpoints. Use the /organizations or tenant-specific endpoint. You used /common. As explained in the message from Azure AD, the authority needs to have a tenant or otherwise /organizations.
MsalServiceException AADSTS70002: The request body must contain the following parameter: client_secret or client_assertion. This exception can be thrown if your application was not registered as a public client application in Azure AD. In the Azure portal, edit the manifest for your application and set allowPublicClient to true.
MsalClientException unknown_user Message: Could not identify logged in user The library was unable to query the current Windows logged-in user or this user isn't AD or AAD joined (work-place joined users aren't supported). Mitigation 1: on UWP, check that the application has the following capabilities: Enterprise Authentication, Private Networks (Client and Server), User Account Information. Mitigation 2: Implement your own logic to fetch the username (for example, john@contoso.com) and use the AcquireTokenByIntegratedWindowsAuth form that takes in the username.
MsalClientException integrated_windows_auth_not_supported_managed_user This method relies on a protocol exposed by Active Directory (AD). If a user was created in Azure Active Directory without AD backing ("managed" user), this method will fail. Users created in AD and backed by AAD ("federated" users) can benefit from this non-interactive method of authentication. Mitigation: Use interactive authentication.

MsalUiRequiredException

One of common status codes returned from MSAL.NET when calling AcquireTokenSilent() is 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.

Most of the time when AcquireTokenSilent fails, it is because the token cache doesn't have tokens matching your request. Access tokens expire in 1 hour, and AcquireTokenSilent will try to fetch a new one based on a refresh token (in OAuth2 terms, this is the "Refresh Token' flow). This flow can also fail for various reasons, for example if a tenant admin configures more stringent login policies.

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

MsalUiRequiredException classification enumeration

MSAL exposes a Classification field, which you can read to provide a better user experience, for example to tell the user that their password expired or that they'll need to provide consent to use some resources. The supported values are part of the UiRequiredExceptionClassification enum:

Classification Meaning Recommended handling
BasicAction Condition can be resolved by user interaction during the interactive authentication flow. Call AcquireTokenInteractively().
AdditionalAction 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.
MessageOnly Condition can't 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.
ConsentRequired User consent is missing, or has been revoked. Call AcquireTokenInteractively() for user to give consent.
UserPasswordExpired User's password has expired. Call AcquireTokenInteractively() so that user can reset their password.
PromptNeverFailed Interactive Authentication was called with the parameter prompt=never, forcing MSAL to rely on browser cookies and not to display the browser. This has failed. Call AcquireTokenInteractively() without Prompt.None
AcquireTokenSilentFailed MSAL SDK doesn't have enough information to fetch a token from the cache. This can be because no tokens are in the cache or an account wasn't found. The error message has more details. Call AcquireTokenInteractively().
None No further details are provided. Condition may be resolved by user interaction during the interactive authentication flow. Call AcquireTokenInteractively().

.NET code example

AuthenticationResult res;
try
{
 res = await application.AcquireTokenSilent(scopes, account)
        .ExecuteAsync();
}
catch (MsalUiRequiredException ex) when (ex.ErrorCode == MsalError.InvalidGrantError)
{
 switch (ex.Classification)
 {
  case UiRequiredExceptionClassification.None:
   break;
  case UiRequiredExceptionClassification.MessageOnly:
  // You might want to call AcquireTokenInteractive(). Azure AD will show a message
  // that explains the condition. AcquireTokenInteractively() will return UserCanceled error
  // after the user reads the message and closes the window. The calling application may choose
  // to hide features or data 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 UiRequiredExceptionClassification.BasicAction:
  // Call AcquireTokenInteractive() so that the user can, for instance accept terms
  // and conditions

  case UiRequiredExceptionClassification.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 UiRequiredExceptionClassification.ConsentRequired:
  // Call AcquireTokenInteractive() for user to give consent.
  
  case UiRequiredExceptionClassification.UserPasswordExpired:
  // Call AcquireTokenInteractive() so that user can reset their password
  
  case UiRequiredExceptionClassification.PromptNeverFailed:
  // You used WithPrompt(Prompt.Never) and this failed
  
  case UiRequiredExceptionClassification.AcquireTokenSilentFailed:
  default:
  // May be resolved by user interaction during the interactive authentication flow.
  res = await application.AcquireTokenInteractive(scopes)
                         .ExecuteAsync(); break;
 }
}

Conditional Access and claims challenges

When getting tokens silently, your application may receive errors when a Conditional Access claims challenge such as MFA policy is required by an API you're trying to access.

The pattern for handling this error is to interactively acquire a token using MSAL. Interactively acquiring a token prompts the user and gives them the opportunity to satisfy the required Conditional Access policy.

In certain cases when calling an API requiring Conditional Access, you can receive a claims challenge in the error from the API. For instance if the Conditional Access policy is to have a managed device (Intune) the error will be something like AADSTS53000: Your device is required to be managed to access this resource or something similar. In this case, you can pass the claims in the acquire token call so that the user is prompted to satisfy the appropriate policy.

.NET

When calling an API requiring Conditional Access from MSAL.NET, your application will need to handle claim challenge exceptions. This will appear as an MsalServiceException where the Claims property won't be empty.

To handle the claim challenge, you'll need to use the .WithClaim() method of the PublicClientApplicationBuilder class.

JavaScript

When getting tokens silently (using acquireTokenSilent) using MSAL.js, your application may receive errors when a Conditional Access claims challenge such as MFA policy is required by an API you're trying to access.

The pattern to handle this error is to make an interactive call to acquire token in MSAL.js such as acquireTokenPopup or acquireTokenRedirect as in the following example:

myMSALObj.acquireTokenSilent(accessTokenRequest).then(function (accessTokenResponse) {
    // call API
}).catch( function (error) {
    if (error instanceof InteractionRequiredAuthError) {
        // Extract claims from error message
        accessTokenRequest.claimsRequest = extractClaims(error.errorMessage);
        // call acquireTokenPopup in case of InteractionRequiredAuthError failure
        myMSALObj.acquireTokenPopup(accessTokenRequest).then(function (accessTokenResponse) {
            // call API
        }).catch(function (error) {
            console.log(error);
        });
    }
});

Interactively acquiring the token prompts the user and gives them the opportunity to satisfy the required Conditional Access policy.

When calling an API requiring Conditional Access, you can receive a claims challenge in the error from the API. In this case, you can pass the claims returned in the error to the claimsRequest field of the AuthenticationParameters.ts class to satisfy the appropriate policy.

See Requesting Additional Claims for more detail.

MSAL for iOS and macOS

MSAL for iOS and macOS allows you to request specific claims in both interactive and silent token acquisition scenarios.

To request custom claims, specify claimsRequest in MSALSilentTokenParameters or MSALInteractiveTokenParameters.

See Request custom claims using MSAL for iOS and macOS for more info.

Retrying after errors and exceptions

You're expected to implement you own retry policies when calling MSAL. MSAL makes HTTP calls to the AAD service, and occasional failures can occur, for example the network can go down or the server is overloaded.

HTTP error codes 500-600

MSAL.NET implements a simple retry-once mechanism for errors with HTTP error codes 500-600.

HTTP 429

When the Service Token Server (STS) is overloaded with too many requests, it returns HTTP error 429 with a hint about how long until you can try again in the Retry-After response field.

.NET

MsalServiceException surfaces System.Net.Http.Headers.HttpResponseHeaders as a property namedHeaders. You can use additional information from the error code to improve the reliability of your applications. In the case described, you can use the RetryAfterproperty (of type RetryConditionHeaderValue) and compute when to retry.

Here is an example for a daemon application using the client credentials flow. You can adapt this to any of the methods for acquiring a token.

do
{
    retry = false;
    TimeSpan? delay;
    try
    {
         result = await publicClientApplication.AcquireTokenForClient(scopes, account)
                                           .ExecuteAsync();
    }
    catch (MsalServiceException serviceException)
    {
         if (ex.ErrorCode == "temporarily_unavailable")
         {
             RetryConditionHeaderValue retryAfter = serviceException.Headers.RetryAfter;
             if (retryAfter.Delta.HasValue)
             {
                 delay = retryAfter.Delta;
             }
             else if (retryAfter.Date.HasValue)
             {
                 delay = retryAfter.Date.Value.Offset;
             }
         }
    }
    …
    if (delay.HasValue)
    {
        Thread.Sleep((int)delay.Value.TotalMilliseconds); // sleep or other
        retry = true;
    }
} while (retry);