Caching Access Tokens

Obtaining an access token can be an expensive operation that could present a perception of a performance issue in web applications. Considering the fact that most partners are developing web application that integrate with the Partner Center API and that it requires an OAuth access token I wanted to provide some guidance on how to cache access tokens. If the access token for the API is not cached then every call to the API will also require a request to obtain an access token. The Fiddler trace below shows the requests generated for a simple two page web application that list the customers on one page and the subscriptions on another page.

image

As you can see there are two requests to obtain an access token. Now this is a simple example, but what if you were developing a control panel there would be far more than two requests to the Partner Center API. When you make the same requests, as shown in the figure above, with token caching you reduce the requests to obtain an access token as shown in the figure below

image

Before we explore how this can be accomplished it is important to understand what is required to obtain an access token for the Partner Center API. An in-depth explanation regarding authentication for this API can be found here. Basically what will need to happen, when you are using App + User authentication, is that you will request an access token from Azure AD and then generate a Partner Center token by making a request to https://api.partnercenter.microsoft.com/GenerateToken. The figure below shows the required request captured by Fiddler

image

Since we have to obtain a token from Azure AD and Partner Center we cannot simplify utilize ADAL’s token caching functionality. Additional logic must be added in order to cache the access token generated to access the Partner Center API. This can be accomplished by using code similar to the following

 
public async Task<IPartnerCredentials> GetPartnerCenterTokenAsync(string authority)
{
    AuthenticationResult authResult;
    IPartnerCredentials credentials;

    if (string.IsNullOrEmpty(authority))
    {
        throw new ArgumentNullException(nameof(authority));
    }

    try
    {
        if (CacheManager.Instance.Exists(Key))
        {
            credentials = JsonConvert.DeserializeObject<PartnerCenterTokenModel>(
                CacheManager.Instance.Read(Key));

            if (!credentials.IsExpired())
            {
                return credentials;
            }
        }

        authResult = await GetAADTokenAsync(
            authority,
            "https://api.partnercenter.microsoft.com");

        credentials = await PartnerCredentials.Instance.GenerateByUserCredentialsAsync(
            Settings.ApplicationId,
            new AuthenticationToken(authResult.AccessToken, authResult.ExpiresOn));

        CacheManager.Instance.Write(Key, JsonConvert.SerializeObject(credentials));

        return credentials;
    }
    finally
    {
        authResult = null;
    }
}

This code will check if an access token exists in the cache already. In the event it does the access token will be check to ensure it is still valid, if it is not then a request to obtain another access token will be made. If it is valid the token will be returned to be utilized in the request. It is worth noting that this code will only handle the caching of the Partner Center token, and in order to cache the required Azure AD token code similar to the following should be leveraged

 
public async Task<AuthenticationResult> GetAADTokenAsync(string authority, string resource)
{
    AuthenticationContext authContext;
    DistributedTokenCache tokenCache;

    if (string.IsNullOrEmpty(authority))
    {
        throw new ArgumentNullException(nameof(authority));
    }
    if (string.IsNullOrEmpty(resource))
    {
        throw new ArgumentNullException(nameof(resource));
    }

    try
    {
        // If the Redis Cache connection string is not populated then utilize the constructor
        // that only requires the authority. That constructor will utilize a in-memory caching
        // feature that is built-in into ADAL.
        if (string.IsNullOrEmpty(Settings.RedisConnection))
        {
            authContext = new AuthenticationContext(authority);
        }
        else
        {
            tokenCache = new DistributedTokenCache(resource);
            authContext = new AuthenticationContext(authority, tokenCache);
        }

        return await authContext.AcquireTokenAsync(
            resource,
            new ClientCredential(
                Settings.ApplicationId,
                Settings.ApplicationSecret),
            new UserAssertion(UserAssertionToken, "urn:ietf:params:oauth:grant-type:jwt-bearer"));
    }
    finally
    {
        authContext = null;
        tokenCache = null;
    }
}

When initializing a new instance of AuthenticationContext you can provide a token cache to lookup cached tokens on calls to AcquireToken. If you do not provide this object then the default in-memory caching technique will be utilized. The ADAL token caching mechanisms will not cache a token that utilizes the common authority (e.g. https://login.microsoftonline.com/common/oauth2/token) so be sure you to use the appropriate authority to obtain the token. The code sample above utilizes a custom token caching class that leverages Azure Redis Cache in order store the access token. This is important because if you are hosting your web application in a web farm or on Azure it possible that requests will go to different servers, and when application pools are reset anything in memory will be lost. So utilizing something like Azure Redis Cache or Azure SQL will allow all servers involved in hosting your application to take advantage of token caching, and it will ensure that if an application pool is reset your token cache is not wiped out.

You can find a sample project that demonstrates these caching techniques here. Hopefully this information will help you improve the performance of your applications!