Error handling best practices for Azure Active Directory Authentication Library (ADAL) clients

Warning

This content is for the older Azure AD v1.0 endpoint. Use the Microsoft identity platform for new projects.

This article provides guidance on the type of errors that developers may encounter, when using ADAL to authenticate users. When using ADAL, there are several cases where a developer may need to step in and handle errors. Proper error handling ensures a great end-user experience, and limits the number of times the end user needs to sign in.

In this article, we explore the specific cases for each platform supported by ADAL, and how your application can handle each case properly. The error guidance is split into two broader categories, based on the token acquisition patterns provided by ADAL APIs:

  • AcquireTokenSilent: Client attempts to get a token silently (no UI), and may fail if ADAL is unsuccessful.
  • AcquireToken: Client can attempt silent acquisition, but can also perform interactive requests that require sign-in.

Tip

It's a good idea to log all errors and exceptions when using ADAL. Logs are not only helpful for understanding the overall health of your application, but are also important when debugging broader problems. While your application may recover from certain errors, they may hint at broader design problems that require code changes in order to resolve.

When implementing the error conditions covered in this document, you should log the error code and description for the reasons discussed earlier. See the Error and logging reference for examples of logging code.

AcquireTokenSilent

AcquireTokenSilent attempts to get a token with the guarantee that the end user does not see a User Interface (UI). There are several cases where silent acquisition may fail, and needs to be handled through interactive requests or by a default handler. We dive into the specifics of when and how to employ each case in the sections that follow.

There is a set of errors generated by the operating system, which may require error handling specific to the application. For more information, see "Operating System" errors section in Error and logging reference.

Application scenarios

Error cases and actionable steps

Fundamentally, there are two cases of AcquireTokenSilent errors:

Case Description
Case 1: Error is resolvable with an interactive sign-in For errors caused by a lack of valid tokens, an interactive request is necessary. Specifically, cache lookup and an invalid/expired refresh token require an AcquireToken call to resolve.

In these cases, the end user needs to be prompted to sign in. The application can choose to do an interactive request immediately, after end-user interaction (such as hitting a sign-in button), or later. The choice depends on the desired behavior of the application.

See the code in the following section for this specific case and the errors that diagnose it.
Case 2: Error is not resolvable with an interactive sign-in For network and transient/temporary errors, or other failures, performing an interactive AcquireToken request does not resolve the issue. Unnecessary interactive sign-in prompts can also frustrate end users. ADAL automatically attempts a single retry for most errors on AcquireTokenSilent failures.

The client application can also attempt a retry at some later point, but when and how is dependent on the application behavior and desired end-user experience. For example, the application can do an AcquireTokenSilent retry after a few minutes, or in response to some end-user action. An immediate retry will result in the application being throttled, and should not be attempted.

A subsequent retry failing with the same error does not mean the client should do an interactive request using AcquireToken, as it does not resolve the error.

See the code in the following section for this specific case and the errors that diagnose it.

.NET

The following guidance provides examples for error handling in conjunction with ADAL methods:

  • acquireTokenSilentAsync(…)
  • acquireTokenSilentSync(…)
  • [deprecated] acquireTokenSilent(…)
  • [deprecated] acquireTokenByRefreshToken(…)

Your code would be implemented as follows:

try{
    AcquireTokenSilentAsync(…);
} 

catch (AdalSilentTokenAcquisitionException e) {
    // Exception: AdalSilentTokenAcquisitionException
    // Caused when there are no tokens in the cache or a required refresh failed. 

    // Action: Case 1, resolvable with an interactive request. 
} 

catch(AdalServiceException e) {
    // Exception: AdalServiceException 
    // Represents an error produced by the STS. 
    // e.ErrorCode contains the error code and description, which can be used for debugging. 
    // NOTE: Do not code a dependency on the contents of the error description, as it can change over time.

    // Action: Case 2, not resolvable with an interactive request.
    // Attempt retry after a timed interval or user action.
} 
    
catch (AdalException e) {
    // Exception: AdalException 
    // Represents a library exception generated by ADAL .NET. 
    // e.ErrorCode contains the error code. 

    // Action: Case 2, not resolvable with an interactive request.
    // Attempt retry after a timed interval or user action.
    // Example Error: network_not_available, default case.
}

Android

The following guidance provides examples for error handling in conjunction with ADAL methods:

  • acquireTokenSilentSync(…)
  • acquireTokenSilentAsync(...)
  • [deprecated] acquireTokenSilent(…)

Your code would be implemented as follows:

// *Inside callback*
public void onError(Exception e) {

    if (e instanceof AuthenticationException) {
        // Exception: AdalException
        // Represents a library exception generated by ADAL Android.
        // Error Code: e.getCode().

        // Errors: ADALError.ERROR_SILENT_REQUEST,
        // ADALError.AUTH_REFRESH_FAILED_PROMPT_NOT_ALLOWED,
        // ADALError.INVALID_TOKEN_CACHE_ITEM
        // Description: Request failed due to no tokens in
        // cache or failed a required refresh. 

        // Action: Case 1, resolvable with an interactive request. 

        // Action: Case 2, not resolvable with an interactive request.
        // Attempt retry after a timed interval or user action.
        // Example Errors: default case,
        // DEVICE_CONNECTION_IS_NOT_AVAILABLE, 
        // BROKER_AUTHENTICATOR_ERROR_GETAUTHTOKEN,
    }
}

iOS

The following guidance provides examples for error handling in conjunction with ADAL methods:

  • acquireTokenSilentWithResource(…)

Your code would be implemented as follows:

[context acquireTokenSilentWithResource:[ARGS], completionBlock:^(ADAuthenticationResult *result) {
    if (result.status == AD_FAILED) {
        if ([error.domain isEqualToString:ADAuthenticationErrorDomain]){
            // Exception: AD_FAILED 
            // Represents a library error generated by ADAL Objective-C.
            // Error Code: result.error.code

            // Errors: AD_ERROR_SERVER_REFRESH_TOKEN_REJECTED, AD_ERROR_CACHE_NO_REFRESH_TOKEN
            // Description: No tokens in cache or failed a required token refresh failed. 
            // Action: Case 1, resolvable with an interactive request. 

            // Error: AD_ERROR_CACHE_MULTIPLE_USERS
            // Description: There was ambiguity in the silent request resulting in multiple cache items.
            // Action: Special Case, application should perform another silent request and specify the user using ADUserIdentifier. 
            // Can be caused in cases of a multi-user application. 

            // Action: Case 2, not resolvable with an interactive request.
            // Attempt retry after some time or user action.
            // Example Errors: default case,
            // AD_ERROR_CACHE_BAD_FORMAT
        }
    }
}]

AcquireToken

AcquireToken is the default ADAL method used to get tokens. In cases where user identity is required, AcquireToken attempts to get a token silently first, then displays UI if necessary (unless PromptBehavior.Never is passed). In cases where application identity is required, AcquireToken attempts to get a token, but doesn't show UI as there is no end user.

When handling AcquireToken errors, error handling is dependent on the platform and scenario the application is trying to achieve.

The operating system can also generate a set of errors, which require error handling dependent on the specific application. For more information, see "Operating System errors" in Error and logging reference.

Application scenarios

  • Native client applications (iOS, Android, .NET Desktop, or Xamarin)
  • Web applications that call a resource API (.NET)
  • Single-page applications (JavaScript)
  • Service-to-Service applications (.NET, Java)
    • All scenarios, including on-behalf-of
    • On-Behalf-of specific scenarios

Error cases and actionable steps: Native client applications

If you're building a native client application, there are a few error handling cases to consider which relate to network issues, transient failures, and other platform-specific errors. In most cases, an application shouldn't perform immediate retries, but rather wait for end-user interaction that prompts a sign-in.

There are a few special cases in which a single retry may resolve the issue. For example, when a user needs to enable data on a device, or completed the Azure AD broker download after the initial failure.

In cases of failure, an application can present UI to allow the end user to perform some interaction that prompts a retry. For instance, if the device failed for an offline error, a "Try to Sign in again" button prompting an AcquireToken retry rather than immediately retrying the failure.

Error handling in native applications can be defined by two cases:

Case Description
Case 1:
Non-Retryable Error (most cases)
1. Do not attempt immediate retry. Present the end-user UI based on the specific error that invokes a retry (for example, "Try to Sign in again" or "Download Azure AD broker application").
Case 2:
Retryable Error
1. Perform a single retry as the end user may have entered a state that results in a success.

2. If retry fails, present the end-user UI based on the specific error that invokes a retry ("Try to Sign in again", "Download Azure AD broker app", etc.).

Important

If a user account is passed to ADAL in a silent call and fails, the subsequent interactive request allows the end user to sign in using a different account. After a successful AcquireToken using a user account, the application must verify the signed-in user matches the applications's local user object. A mismatch does not generate an exception (except in Objective C), but should be considered in cases where a user is known locally before the authentication requests (like a failed silent call).

.NET

The following guidance provides examples for error handling in conjunction with all non-silent AcquireToken(…) ADAL methods, except:

  • AcquireTokenAsync(…, IClientAssertionCertification, …)
  • AcquireTokenAsync(…, ClientCredential, …)
  • AcquireTokenAsync(…, ClientAssertion, …)
  • AcquireTokenAsync(…, UserAssertion,…)

Your code would be implemented as follows:

try {
    AcquireTokenAsync(…);
} 
    
catch(AdalServiceException e) {
    // Exception: AdalServiceException 
    // Represents an error produced by the STS. 
    // e.ErrorCode contains the error code and description, which can be used for debugging. 
    // NOTE: Do not code a dependency on the contents of the error description, as it can change over time.
    
    // Design time consideration: Certain errors may be caused at development and exposed through this exception. 
    // Looking inside the description will give more guidance on resolving the specific issue. 

    // Action: Case 1: Non-Retryable 
    // Do not perform an immediate retry. Only retry after user action. 
    // Example Errors: default case

    } 

catch (AdalException e) {
    // Exception: AdalException 
    // Represents a library exception generated by ADAL .NET.
    // e.ErrorCode contains the error code

    // Action: Case 1, Non-Retryable 
    // Do not perform an immediate retry. Only retry after user action. 
    // Example Errors: network_not_available, default case
}

Note

ADAL .NET has an extra consideration as it supports PromptBehavior.Never, which has behavior like AcquireTokenSilent.

The following guidance provides examples for error handling in conjunction with ADAL methods:

  • acquireToken(…, PromptBehavior.Never)

Your code would be implemented as follows:

    try {acquireToken(…, PromptBehavior.Never);
    } 

catch(AdalServiceException e) {
    // Exception: AdalServiceException represents 
    // Represents an error produced by the STS. 
    // e.ErrorCode contains the error code and description, which can be used for debugging. 
    // NOTE: Do not code a dependency on the contents of the error description, as it can change over time.

    // Action: Case 1: Non-Retryable 
    // Do not perform an immediate retry. Only retry after user action. 
    // Example Errors: default case

} catch (AdalException e) {
    // Error Code: e.ErrorCode == "user_interaction_required"
    // Description: user_interaction_required indicates the silent request failed 
    // in a way that's resolvable with an interactive request.
    // Action: Resolvable with an interactive request. 

    // Action: Case 1, Non-Retryable 
    // Do not perform an immediate retry. Only retry after user action. 
    // Example Errors: network_not_available, default case
}

Android

The following guidance provides examples for error handling in conjunction with all non-silent AcquireToken(…) ADAL methods.

Your code would be implemented as follows:

AcquireTokenAsync(…);

// *Inside callback*
public void onError(Exception e) {
    if (e instanceof AuthenticationException) {
        // Exception: AdalException 
        // Represents a library exception generated by ADAL Android.
        // Error Code: e.getCode();

        // Error: ADALError.BROKER_APP_INSTALLATION_STARTED
        // Description: Broker app not installed, user will be prompted to download the app. 

        // Action: Case 2, Retriable Error 
        // Perform a single retry. If that fails, only try again after user action. 

        // Action: Case 1, Non-Retriable 
        // Do not perform an immediate retry. Only retry after user action. 
        // Example Errors: default case, DEVICE_CONNECTION_IS_NOT_AVAILABLE
    }
}

iOS

The following guidance provides examples for error handling in conjunction with all non-silent AcquireToken(…) ADAL methods.

Your code would be implemented as follows:

[context acquireTokenWithResource:[ARGS], completionBlock:^(ADAuthenticationResult *result) {
    if (result.status == AD_FAILED) {
        if ([error.domain isEqualToString:ADAuthenticationErrorDomain]){
            // Exception: AD_FAILED 
            // Represents a library error generated by ADAL ObjC.
            // Error Code: result.error.code 

            // Error: AD_ERROR_SERVER_WRONG_USER
            // Description: App passed a user into ADAL and the end user signed in with a different account. 
            // Action: Case 1, Non-retriable (as is) and up to the application on how to handle this case. 
            // It can attempt a new request without specifying the user, or use UI to clarify the user account to sign in. 

            // Action: Case 1, Non-Retriable 
            // Do not perform an immediate retry. Only retry after user action. 
            // Example Errors: default case
        }
    }
}]

Error cases and actionable steps: Web applications that call a resource API (.NET)

If you're building a .NET web app that calls gets a token using an authorization code for a resource, the only code required is a default handler for the generic case.

The following guidance provides examples for error handling in conjunction with ADAL methods:

  • AcquireTokenByAuthorizationCodeAsync(…)

Your code would be implemented as follows:

try {
    AcquireTokenByAuthorizationCodeAsync(…);
} 

catch (AdalException e) {
    // Exception: AdalException
    // Represents a library exception generated by ADAL .NET.
    // Error Code: e.ErrorCode

    // Action: Do not perform an immediate retry. Only try again after user action or wait until much later. 
    // Example Errors: default case
}

Error cases and actionable steps: Single-page applications (adal.js)

If you're building a single-page application using adal.js with AcquireToken, the error handling code is similar to that of a typical silent call. Specifically in adal.js, AcquireToken never shows a UI.

A failed AcquireToken has the following cases:

Case Description
Case 1:
Resolvable with an interactive request
1. If login() fails, do not perform immediate retry. Only retry after user action prompts a retry.
Case 2:
Not Resolvable with an interactive request. Error is retryable.
1. Perform a single retry as the end user major have entered a state that results in a success.

2. If retry fails, present the end user with an action based on the specific error that can invoke a retry ("Try to Sign in again").
Case 3:
Not Resolvable with an interactive request. Error is not retryable.
1. Do not attempt immediate retry. Present the end user with an action based on the specific error that can invoke a retry ("Try to Sign in again").

Your code would be implemented as follows:

AuthContext.acquireToken(…, function(error, errorDesc, token) {
    if (error || errorDesc) {
        // Represents any token acquisition failure that occurred. 
        // Error Code: error.indexOf("<ERROR_STRING>")

        // Errors: if (error.indexOf("interaction_required"))
        //         if (error.indexOf("login required"))
        // Description: ADAL wasn't able to silently acquire a token because of expire or fresh session. 
        // Action: Case 1, Resolvable with an interactive login() request. 

        // Error: if (error.indexOf("Token Renewal Failed")) 
        // Description: Timeout when refreshing the token.
        // Action: Case 2, Not resolvable interactively, error is retriable.
        // Perform a single retry. Only try again after user action.

        // Action: Case 3, Not resolvable interactively, error is not retriable. 
        // Do not perform an immediate retry. Only retry after user action.
        // Example Errors: default case
    }
}

Error cases and actionable steps: service-to-service applications (.NET only)

If you're building a service-to-service application that uses AcquireToken, there are a few key errors your code must handle. The only recourse to failure is to return the error back to the calling app (for on-behalf-of cases) or apply a retry strategy.

All scenarios

For all service-to-service application scenarios, including on-behalf-of:

  • Do not attempt an immediate retry. ADAL attempts a single retry for certain failed requests.
  • Only continue retrying after a user or app action is prompts a retry. For example, a daemon application that does work on some set interval should wait until the next interval to retry.

The following guidance provides examples for error handling in conjunction with ADAL methods:

  • AcquireTokenAsync(…, IClientAssertionCertification, …)
  • AcquireTokenAsync(…,ClientCredential, …)
  • AcquireTokenAsync(…,ClientAssertion, …)
  • AcquireTokenAsync(…,UserAssertion, …)

Your code would be implemented as follows:

try {
    AcquireTokenAsync(…);
} 

catch (AdalException e) {
    // Exception: AdalException
    // Represents a library exception generated by ADAL .NET.
    // Error Code: e.ErrorCode

    // Action: Do not perform an immediate retry. Only try again after user action (if applicable) or wait until much later. 
    // Example Errors: default case
}  

On-behalf-of scenarios

For on-behalf-of service-to-service application scenarios.

The following guidance provides examples for error handling in conjunction with ADAL methods:

  • AcquireTokenAsync(…, UserAssertion, …)

Your code would be implemented as follows:

try {
AcquireTokenAsync(…);
} 

catch (AdalServiceException e) {
    // Exception: AdalServiceException 
    // Represents an error produced by the STS. 
    // e.ErrorCode contains the error code and description, which can be used for debugging. 
    // NOTE: Do not code a dependency on the contents of the error description, as it can change over time.

    // Error: On-Behalf-Of Error Handler
    if (e.ErrorCode == "interaction_required") {
        // Description: The client needs to perform some action due to a config from admin. 
        // Action: Capture `claims` parameter inside ex.InnerException.InnerException.
        // Generate HTTP error 403 with claims, throw back HTTP error to client.
        // Wait for client to retry. 
    }
} 
        
catch (AdalException e) {
    // Exception: AdalException 
    // Represents a library exception generated by ADAL .NET.
    // Error Code: e.ErrorCode

    // Action: Do not perform an immediate retry. Only try again after user action (if applicable) or wait until much later. 
    // Example Error: default case
}

We've built a complete sample that demonstrates this scenario.

Error and logging reference

Logging Personal Identifiable Information & Organizational Identifiable Information

By default, ADAL logging does not capture or log any personal identifiable information or organizational identifiable information. The library allows app developers to turn this on through a setter in the Logger class. By logging personal identifiable information or organizational identifiable information, the app takes responsibility for safely handling highly sensitive data and complying with any regulatory requirements.

.NET

ADAL library errors

To explore specific ADAL errors, the source code in the azure-activedirectory-library-for-dotnet repository is the best error reference.

Guidance for error logging code

ADAL .NET logging changes depending on the platform being worked on. Refer to the Logging wiki for code on how to enable logging.

Android

ADAL library errors

To explore specific ADAL errors, the source code in the azure-activedirectory-library-for-android repository is the best error reference.

Operating System errors

Android OS errors are exposed through AuthenticationException in ADAL, are identifiable as "SERVER_INVALID_REQUEST", and can be further granular through the error descriptions.

For a full list of common errors and what steps to take when your app or end users encounter them, refer to the ADAL Android Wiki.

Guidance for error logging code

// 1. Configure Logger
Logger.getInstance().setExternalLogger(new ILogger() {    
    @Override   
    public void Log(String tag, String message, String additionalMessage, LogLevel level, ADALError errorCode) { 
    // …
    // You can write this to logfile depending on level or errorcode. 
    writeToLogFile(getApplicationContext(), tag +":" + message + "-" + additionalMessage);    
    }
}

// 2. Set the log level
Logger.getInstance().setLogLevel(Logger.LogLevel.Verbose);

// By default, the `Logger` does not capture any PII or OII

// PII or OII will be logged
Logger.getInstance().setEnablePII(true);

// To STOP logging PII or OII, use the following setter
Logger.getInstance().setEnablePII(false);


// 3. Send logs to logcat.
adb logcat > "C:\logmsg\logfile.txt";

iOS

ADAL library errors

To explore specific ADAL errors, the source code in the azure-activedirectory-library-for-objc repository is the best error reference.

Operating system errors

iOS errors may arise during sign-in when users use web views, and the nature of authentication. This can be caused by conditions such as TLS errors, timeouts, or network errors:

  • For Entitlement Sharing, logins are not persistent and the cache appears empty. You can resolve by adding the following line of code to the keychain: [[ADAuthenticationSettings sharedInstance] setSharedCacheKeychainGroup:nil];
  • For the NsUrlDomain set of errors, the action changes depending on the app logic. See the NSURLErrorDomain reference documentation for specific instances that can be handled.
  • See ADAL Obj-C Common Issues for the list of common errors maintained by the ADAL Objective-C team.

Guidance for error logging code

// 1. Enable NSLogging
[ADLogger setNSLogging:YES];

// 2. Set the log level (if you want verbose)
[ADLogger setLevel:ADAL_LOG_LEVEL_VERBOSE];

// 3. Set up a callback block to simply print out
[ADLogger setLogCallBack:^(ADAL_LOG_LEVEL logLevel, NSString *message, NSString *additionalInformation, NSInteger errorCode, NSDictionary *userInfo) {
     NSString* log = [NSString stringWithFormat:@"%@ %@", message, additionalInformation];    
     NSLog(@"%@", log);
}];

Guidance for error logging code - JavaScript

0: Error1: Warning2: Info3: Verbose
window.Logging = {
    level: 3,
    log: function (message) {
        console.log(message);
    }
};

Use the comments section that follows, to provide feedback and help us refine and shape our content.

Shows the "Sign in with Microsoft" button