Saturday, August 24, 2019

AcquireTokenSilentAsync using a cached token

Token are cached

Once ADAL.NET has acquired a token for a user for a Web API, it caches it, along with a Refresh token. Next time the application wants a token, it can first call AcquireTokenSilentAsync to verify if an acceptable token is in the cache:
  • If there is such a token and it has not expired, it's returned, which is fast
  • If there is a token but it has expired, AcquireTokenSilentAsync will use the cached refresh token in order to refresh the access token (request another access token and refresh token from Azure AD).
  • If no token is in the cache, AcquireTokenSilentAsync will throw an AdalSilentTokenAcquisitionException, and the application will need to calls AcquireTokenAsync (which might require some user interaction)
Note ADAL v2 used to expose the refresh token because you had to handle refresh yourself. With ADAL v3, this is done automatically by the library

AcquireTokenAsync gets token from the cache

Like AcquireTokenAsync, in ADAL.NET, AcquireTokenSilentAsync has several overrides, which are not available for public client applications. You'll see them later. For the moment, let's get interested in the last three overrides, which are for public client applications.
image
Unsurprisingly, the overrides are similar to the public application client overrides of AcquireTokenAsync, and they take as parameters:
  • The resource for which you want an access token. you can pass here either the Resource URI of a Web API, or the clientId of the target Web API. Both work.
  • The clientId parameter is the clientId/applicationId of the public client application.
  • userId is optional. It's still set from the DisplayableId of a user
  • parameters is also still optional to describe the desired user interaction (See the same parameter AcquireTokenAsync). If you specify PromptBehaviors which would induce some user interaction (AlwaysSelectAccount), the token acquisition is, nevertheless done silently (equivalent to RefreshSession). This behavior is because AcquireTokenSilent is ... silent, and therefore there is no user interaction.

Recommended pattern to acquire a token

Now that you have seen both AcquireTokenAsyncAcquireTokenSilentAsync, it's the right moment to present the recommended usage pattern for calling these methods. The idea is that you want to minimize the number of signings for the user, and therefore you'd want to:
  • first try to acquire a token silently,
  • and if this call fails you try to get one interactively.
Note thatAcquireTokenSilent does not need to be called in the Client credentials flow (when the application acquires token without a user, but in its own name)
Note that AcquireTokenSilent can fail for several reasons, such as the cache does not contain a token for the user, or the token has expired and cannot be refreshed. For these reasons, a call to AcquireTokenAsync will usually get a token. But there are also issues such as network problems, STS unavailability, etc., which won't be directly solvable. You will see them in more details in the article about best practices for Handling errors.
In ADAL.NET the recommended pattern is the following code snippet for public client applications:
// STS
string cloud = "https://login.microsoftonline.com";
string tenantId = "331e6716-26e8-4651-b323-2563936b416e";
string authority = $"{cloud}/{tenantId}";

// Application
string clientId = "65b27a1c-693c-44bf-bf92-c49e408ccc70";
Uri redirectUri = new Uri("https://TodoListClient");

// Application ID of the Resource (could also be the Resource URI)
string resource = "eab51d24-076e-44ee-bcf0-c2dce7577a6a";

AuthenticationContext ac = new AuthenticationContext(authority);
AuthenticationResult result=null;
try
{
 result = await ac.AcquireTokenSilentAsync(resource, clientId);
}
catch (AdalException adalException)
{
 if (adalException.ErrorCode == AdalError.FailedToAcquireTokenSilently
     || adalException.ErrorCode == AdalError.InteractionRequired)
  {
   result = await ac.AcquireTokenAsync(resource, clientId, redirectUri,
                                       new PlatformParameters(PromptBehavior.Auto));
  }
}

No comments:

Post a Comment

No String Argument Constructor/Factory Method to Deserialize From String Value

  In this short article, we will cover in-depth the   JsonMappingException: no String-argument constructor/factory method to deserialize fro...