ADAL.NET 3.17.0 released

Publikováno dne 11 října, 2017

Senior Program Manager - Identity division

ADAL.NET (Microsoft.IdentityModel.Clients.ActiveDirectory) is an authentication library which enables developers to acquire tokens from Azure AD and ADFS to access Microsoft APIs or applications registered with Azure Active Directory. ADAL.NET is available on several .NET platforms including Desktop, Universal Windows Platform, Xamarin / Android, Xamarin iOS, Portable Class Libraries, and .NET Core. We just released ADAL.NET 3.17.0 which enables new capabilities and brings improvements in terms of usability, privacy, and performance.

Enabling new capabilities

ADAL.Net 3.17.0 enables you to:

  • Write more efficient applications, tolerant to Azure AD throttling.
  • Force end users of your apps to choose an identity even when s/he is logged-in.
  • Process more effective conditional access.

Enabling more efficient applications (Retry-After for instance)

You might have seen some of our samples processing, acquiring, and catching an AdalException with an ErrorCode "temporarily_unavailable". When the Service Token Server (STS) is too busy because of “too many requests”, it returns an HTTP error 429 with a hint about when you can try again (Retry-After response field) as a delay in seconds, or a date.

Previously, ADAL.NET did not surface this information. Therefore, to handle the error we advised to retry an arbitrary number of times after waiting for a hard-coded arbitrary delay. For a console application, the code could look like the following:

do
{
    retry = false;
    try
    {
        result = await authContext.AcquireTokenAsync(resource, certCred);
    }
    catch (AdalException ex)
    {
        if (ex.ErrorCode == "temporarily_unavailable")
        {
            retry = true;
            retryCount++;
            Thread.Sleep(3000);
        }
	  …
    }
     …
} while ((retry == true) && (retryCount < 2)); 

From ADAL.NET 3.17.0, we are now surfacing the System.Net.Http.Headers.HttpResponseHeaders as a new property named Headers in the AdalServiceException. Therefore, you can leverage additional information to improve the reliability of your applications. In the case we just described, you can use the RetryAfter property (of type RetryConditionHeaderValue) and compute when to retry.

Note that depending on whether you are using ADAL.Net for a confidential client application or a public client application, you will have to catch the AdalServiceException directly or as an InnerException of the AdalException.

The following code snippet should give you an idea of how to proceed depending on the case:

do
{
    retry = false;
    TimeSpan ?delay;
    try
    {
        result = await authContext.AcquireTokenAsync(resource, certCred);
    }

    // Case of a Confidential client flow 
    // (for instance auth code redemption in a Web App)
    catch (AdalServiceException serviceException)
    {
	 if (ex.ErrorCode == "temporarily_unavailable")
        {
		RetryConditionHeaderValue retry= serviceException.Headers.RetryAfter;
		if (retry.Delta.HasValue)
              {
			delay = retry.Delta;
              }
		else if (retry.Date.HasValue)
              {
			delay = retry.Date.Value.Offset;
              }
        }

    }
    
    // Case of a client side exception
    catch (AdalException ex)
    {
        if (ex.ErrorCode == "temporarily_unavailable")
        {
     var serviceEx = ex.InnerException as AdalServiceException;
    // Same kind of processing as above
        }
	…
    }
    …
   if (delay.HasValue)
   {
	Thread.Sleep((int)delay.Value.TotalSeconds); // sleep or other
retry = true;
   }

} while (retry); 

Forcing the user to select an account

More people are using multiple personal, professional, and organization identities. You might have a use case in your application where you want your user to choose which identity to use. To enable such use cases, we added a new value SelectAccount in the PromptBehavior enumeration for the platforms supporting interaction (Desktop, WinRT, Xamarin iOS, and Xamarin Android). If you use it, you will force your app’s user to choose an account, even when s/he is already logged-in, bypassing the cache lookup, and presenting the UI directly.

You might have used PromptBehavior.Always in the past, which also bypasses the token cache and presents a User interface. PromptBehavior.SelectAccount is different because it tells Azure AD to display available users as tiles and does not force users to sign in again (assuming the cookies are available, remember the interaction between the user and Azure AD happens in a browser). The presence of tiles does not guarantee a Single Sign On experience because the behavior is determined by the cookie lifetime that it managed completely outside the library’s purview.

Enabling your applications to handle conditional access (and other claim challenges)

We try to keep most of our samples simple, however, you probably know that if you want to produce enterprise ready applications, you will have to put a bit more effort into error handling. To that effect, in ADAL.NET 3.16.0, we enable you to process claim challenges sent by Azure AD when your application needs to involve the user to let him accept that the application access additional resources, or to let him do multi-factor authentication. In ADAL.NET 3.17.0, we enabled this feature by passing back to the API caller the HttpRequestWrapperException as an inner exception to AdalClaimChallengeException so that you can get missing claims. You can then pass these additional claims to the acquireToken overrides which have a new claims member.

The code snippet below is extracted from the active-directory-dotnet-webapi-onbehalfof-ca sample. It illustrates the claim challenge received from Azure AD by the TodoList service (confidential client) and how this challenge is propagated to the clients so that they can, in their turn, have the needed user interaction (for instance two-factor authentication).

try
{
    result = await authContext.AcquireTokenAsync(caResourceId, clientCred,
                                                 userAssertion);
}
catch (AdalClaimChallengeException ex)
{
    HttpResponseMessage myMessage = new HttpResponseMessage 
    { StatusCode = HttpStatusCode.Forbidden, ReasonPhrase = INTERACTION_REQUIRED,
      Content = new StringContent(ex.Claims) };
    throw new HttpResponseException(myMessage);
}
catch (AdalServiceException ex)
{
  …

On the client side (TodoListClient), the code getting this challenge when calling the TodoList service and re-requesting the token with more claims is the following:

// We successfully got a token.
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);

// while calling the API.
HttpResponseMessage response = await httpClient.GetAsync(todoListBaseAddress 
                                                         + "/api/AccessCaApi");

if (response.StatusCode == System.Net.HttpStatusCode.Forbidden 
    && response.ReasonPhrase == INTERACTION_REQUIRED)
 {
     // We need to re-request the token to account for a Conditional Access Policy
     String claimsParam = await response.Content.ReadAsStringAsync();

    try
    {
     result = await authContext.AcquireTokenAsync(todoListResourceId, clientId,
                     redirectUri, new PlatformParameters(PromptBehavior.Always), 
                     new UserIdentifier(displayName,
                                        UserIdentifierType.OptionalDisplayableId),
                     extraQueryParameters:null, 
                     claims: claimsParam);
     …

More details are described in the Developer Guidance for Azure Active Directory Conditional Access and the samples linked from this article.

Usability improvements

ADAL.NET NuGet package now contains one DLL for each platform

ADAL.NET used to be packaged as one common assembly which dynamically loaded another platform specific assembly using dependency injection. This was causing issues (like #511). When you were referencing the NuGet package from a portable library, you also had to reference the platform specific assembly from your main assembly, which was not very intuitive.

Starting with ADAL.NET 3.17.0, the NuGet package now contains a single DLL for each platform.

In case you are interested in the implementation details, have a look at ADAL.NET’s source code on GitHub, you’ll see that we’ve moved to a multi-target project for ADAL.NET.

Removing confusion by hiding the APIs which did not make sense in some platforms

WinRT Apps can now only use one ClientCredential constructor

Even if WinRT applications are generally public client applications, they can also use client credential flow to enable kiosk mode scenarios where no user is logged-in. So far the ClientCredential class used in confidential client scenarios had two overrides:

  • One with application secret, public ClientCredential(string clientId, string clientSecret).
  • One that redeems an authorizationcode or pass in a user assertion.

The later did not make sense for WinRT applications. It’s now only available on desktop applications.

Device Profile API is now only available in Desktop, .NET core and UWP apps

The Device Profile API AcquireTokenByDeviceCodeAsync(DeviceCodeResult deviceCodeResult) acquires a security token from the STS using a device code previously requested using one of the overrides of AcquireDeviceCodeAsync (See Invoking an API protected by Azure AD from a text-only device). AcquireTokenByDeviceCodeAsync is no longer available for Xamarin iOS and Xamarin Android which are not text-only devices. It should only be used in desktop, .Net Core and UWP (for IoT) apps. This is making things more consistent as AcquireDeviceCodeAsync was already not available for Android and iOS.

Improved documentation

We fixed a number of issues in the reference documentation, which was confusing for UserPasswordCredential and AcquireToken (See #654). We also updated the library’s readme.md with instructions on how to enable logging by implementing the IAdalLogCallback interface, and how to interact with an external broker (case of Xamarin iOS / Xamarin Android).

Privacy and performance improvements

As you might know, you can activate Logging in ADAL.NET logger by assigning to LoggerCallbackHandler. Callback is an instance of a class implementing the IAdalLogCallback interface. When you did that, and chose to see Verbose information, when ADAL.Net was sending a request to Azure AD, you could see two messages.

  • “Navigating to ‘complete URL’”
  • “Navigated to ‘complete URL’”

Where ‘Complete URL’ was the complete URL sent to Azure AD. Including, with some prompt behaviors, personal information such as the User Principal Name (UPN) of the user.

We improved privacy by no longer logging the complete URL.

We also improved performance by fixing a memory leak specific to the Xamarin iOS platform.

In closing

As usual we’d love to hear your feedback. Please:

  • Ask questions on  Stack Overflow using the ADAL tag.
  • Use GitHub Issues on the ADAL.Net open source repository to report bugs or request features
  • Use the User Voice page to provide recommendations and/or feedback