Card image cap

Using IMemoryCache for Token Caching in an ASP.NET Core Application

ASP.NET Core  • Posted 5 months ago

Recently I was working on a requirement in an ASP.NET Core application which needed to invoke a third-party API for fetching some JSON content. The implementation was pretty simple, I had to invoke an external API for every input request and return the records returned by the API; pretty much the role of a gateway API for underlying services. But this implementation had a catch, the underlying API which needed to be invoked had an OAuth token authentication in place; and hence for every request made to the underlying API I had to pass a valid non-expired OAuth access token to the API to fetch the data. And this token expires after a certain period which needed to be fetched from an OAuth token endpoint. Now the real question lies in how I need to persist this fetched token until its expiry without having to call the OAuth API each time a request for data is made. In such scenarios of persisting less frequently changed data (such as tokens which may be unchanged for few minutes for example) we can make use of the in-memory caching feature that comes with ASP.NET Core.

How In-Memory Caching works:

So far we have seen what a cache means and how it can help us in improving the app performance. The In-Memory cache provided within the application also works in similar ways, it can hold small chunks of data for predefined duration so as to reuse the already existing dataset for repititive calls. However, since it is stored in the memory of the running application (unlike external cache managers such as Redis and such) we should be careful about the size of the dataset we're storing in the cache.

Read: Configuring Response Cache in an ASP.NET Core API

UseCase - Adding Cache in to our Application:

For abstraction sake, I create a utility class TokenService which implements an interface ITokenService containing a single method FetchToken that returns a string value.


namespace ReadersApi
{
    public interface ITokenService
    {
        string FetchToken();
    }
}

Now this is implemented as below:


namespace ReadersApi
{
    public class TokenService : ITokenService
    {
        private readonly IMemoryCache cache;

        public TokenService(IMemoryCache cache)
        {
            this.cache = cache;
        }

        public string FetchToken()
        {
            string token = string.Empty;

            // if cache doesn't contain 
            // an entry called TOKEN
            // error handling mechanism is mandatory
            if (!cache.TryGetValue("TOKEN", out token))
            {
                var tokenmodel = this.GetTokenFromApi();

                // keep the value within cache for 
                // given amount of time
                // if value is not accessed within the expiry time
                // delete the entry from the cache
                var options = new MemoryCacheEntryOptions()
                        .SetAbsoluteExpiration(
                              TimeSpan.FromSeconds(tokenmodel.ExpiresIn));

                cache.Set("TOKEN", tokenmodel.Value, options);

                token = tokenmodel.Value;
            }

            return token;
        }

        private Token GetTokenFromApi()
        {
            // get api implementation happens here
            // returns a token model
        }
    }
}

Where the Token is a model class as below:


class Token
{
    public string Value { get; set; }
    public int ExpiresIn { get; set; }
}

I then register this TokenService as a singleton service, along with registering memory cache as a service to the pipeline.


namespace ReadersApi
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMemoryCache();
            services.AddSingleton<ITokenService, TokenService>();
        }
    }
}


Adding MemoryCache is not necessary required because it is enabled by default for all controllers and other services which are added by Add<>() method. However, for other classes or components which may not be calling the Add<>() method, AddMemoryCache() might be required. Hence I make it a practice to add it explicitly.

Now when I'm required to use the token for my API request within my application, I simply inject the TokenService via constructor and call the FetchToken() method, which checks for any available cache that has not expired. If available, will return the cache value directly; else would fallback on the token retrieval logic we have employed in case if the token expires. The received token is then set to a new cache object and stored in-memory and the token is returned back to the caller.

public class HttpService
{
    private ITokenService token;

    public HttpService(ITokenService token)
    {
        this.token = token;
    }

    public async Task<IEnumerable<Reader>> GetReadersFromExternalApi(
                                                            string requestUrl)
    {
        using (HttpClient client = new HttpClient())
        {
            var request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
            request.Headers.Add("Authorization", 
                  $"Bearer {token.FetchToken()}");
            var res = await client.SendAsync(request);
            var readers = JsonConvert.DeserializeObject<List<Reader>>(
                              await res.Content.ReadAsStringAsync());
            return readers;
        }
    }
}

Cache Expiry - Sliding vs Absolute:

While creating an In-Memory cache object, we are to specify how the cache expiry needs to be handled. When we set a SlidingExpiry to the cache object, the object expiry is extended if the cache is accessed atleast once within the provided cache expiry time. But this comes with a threat that, if the cache object is accessed occasionally or frequently, chances are that the cache never expires (since its always extended) which might be a problem for certain scenarios (such as the one above). The Absolute Expiry on the other hand expires a cache absolutely when the cache expires irrespective of how it is being accessed. This can be quite helpful in cases when the cache needs to be strictly expired when time ends.

In some scenarios, we might need the best of both worlds and hence we can specify both together as below:

var cacheEntry = _cache.GetOrCreate("TOKEN", entry =>
{
    // set a sliding initial expiry of 1 minute
    // assuming that the token expiry is above 1 minute
    entry.SetSlidingExpiration(TimeSpan.FromSeconds(60));

    // set absolute expiry relative to current time
    // makes sure that the extended expiry doesn't exceed 
    // the actual expiry time of the token
    entry.AbsoluteExpirationRelativeToNow 
                  = TimeSpan.FromSeconds(tokenmodel.ValidTill);
    return DateTime.Now;
}); 

In this way, I used in-memory caching via IMemoryCache to help persist my intermediate access token for calling external APIs within my application, without having to call the token API repeatedly.

We use cookies to provide you with a great user experience, analyze traffic and serve targeted promotions.   Learn More   Accept