Cache IdentityServer4 Access Tokens in Blazor, MVC, or Razor Pages
Tags: csharp, dotnet, tutorial, webdev
Published: 2019-12-20 17:02:47

---
title: Cache IdentityServer4 Access Tokens in Blazor, MVC, or Razor Pages
published: true
tags: csharp, dotnet, tutorial, webdev
canonical_url: https://wellsb.com/csharp/aspnet/cache-identityserver4-access-token-blazor/
---

In this tutorial, you will add access token caching to your IdentityServer4 protected API in order to reduce unnecessary load on your authentication server.

Be sure you are caught up by reviewing [Part 1](https://wellsb.com/csharp/aspnet/blazor-httpclientfactory-and-web-api/) and [Part 2](https://wellsb.com/csharp/aspnet/blazor-consume-identityserver4-protected-api/) of this tutorial series.

**In this Blazor tutorial series**
- [Part 1: Blazor with Web API Solution Structure](https://wellsb.com/csharp/aspnet/blazor-httpclientfactory-and-web-api/)
- [Part 2: Consume API protected by IdentityServer4](https://wellsb.com/csharp/aspnet/blazor-consume-identityserver4-protected-api/)
- Part 3: Cache IdentityServer4 API Access Token (_Coming Soon_)
- Part 4: The Easy Way (_Coming Soon_)

## Configure Access Token Cache

Start by launching the **BlazorContacts** solution. In the Solution Explorer, expand the **BlazorContacts.Web** project. Add a new class to the **Services** directory and call it **ApiTokenCacheService.cs**.

Add a few `using` directives in order to reference some namespaces you will be using in this class.

```csharp
using IdentityModel.Client;
using Microsoft.Extensions.Caching.Distributed;
using System.Net.Http;
using System.Text.Json;

```

Next, declare some variables and build the constructor for this service. In this example, we will cache the access token for `1` day.

```csharp
private readonly HttpClient _httpClient;
 
private static readonly Object _lock = new Object();
private IDistributedCache _cache;
 
private const int cacheExpirationInDays = 1;
 
private class AccessTokenItem
{
    public string AccessToken { get; set; } = string.Empty;
    public DateTime Expiry { get; set; }
}
 
public ApiTokenCacheService(
    IHttpClientFactory httpClientFactory,
    IDistributedCache cache)
{
    _httpClient = httpClientFactory.CreateClient();
    _cache = cache;
}
```

We need to define a method that will follow the following logic: It should first check if there is a valid access token in the cache. If there is, it should return that token. If there is not, it should request a new token, add the new token to the cache, and return the token. Take a look at the following.

```csharp
public async Task<string> GetAccessToken(string client_name, string api_scope, string secret)
{
    AccessTokenItem accessToken = GetFromCache(client_name);
 
    if (accessToken != null && accessToken.Expiry > DateTime.UtcNow)
    {
        return accessToken.AccessToken;
    }
 
    // Token not cached, or token is expired. Request new token from auth server
    AccessTokenItem newAccessToken = await RequestNewToken(client_name, api_scope, secret);
    AddToCache(client_name, newAccessToken);
 
    return newAccessToken.AccessToken;
}
```

Notice, I went ahead and abstracted this method by passing in relevant values as arguments instead of harcoding them in the method. This will be useful later if I ever decide to use this caching service to fetch access tokens from other APIs.

There are three methods referenced above that we have not yet defined, `GetFromCache()`, `RequestNewToken()`, and `AddToCache()`.

You previously declared an interface for a `DistributedCache`. Use this cache to fetch a value by key.

```csharp
private AccessTokenItem GetFromCache(string key)
{
    var item = _cache.GetString(key);
    if (item != null)
    {
        return JsonSerializer.Deserialize<AccessTokenItem>(item);
    }
 
    return null;
}
```

This method uses the `System.Text.Json` serializer to deserialize and return an `AccessTokenItem` object retrieved from `_cache`.

The `AddToCache()` method should look similar to the following.

```csharp
private void AddToCache(string key, AccessTokenItem accessTokenItem)
{
    var options = new DistributedCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromDays(cacheExpirationInDays));
 
    lock (_lock)
    {
        _cache.SetString(key, JsonSerializer.Serialize(accessTokenItem), options);
    }
}
```

To customize the cache timeout, feel free to modify the `DistributedCacheEntryOptions` by using `TimeSpan.FromHours` instead of `FromDays`, for example.

Finally, take the `RequestNewToken()` method you previously included in **ApiService.cs** and move it to **ApiTokenCacheService.cs**. This will ensure all access token related code is in the same place, making the code more manageable. I have made a couple small changes, because this method should return an `AccessTokenItem`, which includes expiration date, instead of just the access token `string` we used in the previous lesson.

```csharp
private async Task<AccessTokenItem> RequestNewToken(string client_name, string api_scope, string secret)
{
    try
    {
        var discovery = await HttpClientDiscoveryExtensions.GetDiscoveryDocumentAsync(
            _httpClient, "http://localhost:5000");
 
        if (discovery.IsError)
        {
            throw new ApplicationException($"Error: {discovery.Error}");
        }
 
        var tokenResponse = await HttpClientTokenRequestExtensions.RequestClientCredentialsTokenAsync(_httpClient, new ClientCredentialsTokenRequest
        {
            Scope = api_scope,
            ClientSecret = secret,
            Address = discovery.TokenEndpoint,
            ClientId = client_name
        });
 
        if (tokenResponse.IsError)
        {
            throw new ApplicationException($"Error: {tokenResponse.Error}");
        }
 
        return new AccessTokenItem
        {
            Expiry = DateTime.UtcNow.AddSeconds(tokenResponse.ExpiresIn),
            AccessToken = tokenResponse.AccessToken
        };
    }
    catch (Exception e)
    {
        throw new ApplicationException($"Exception {e}");
    }
}
```

## Register Caching Service

Because the access token, in our configuration, is granted at the application level, the same token will be used for all users of the application. By registering the caching service as a Singleton service, each user of the authorized Blazor application, will be able to use the access token, whether it be from the cache or a new token.

Open **Startup.cs** of **BlazorContacts.Web** to register the access token caching service. In the ConfigureServices() method, add the following line:

```csharp
services.AddSingleton<Services.ApiTokenCacheService>();

```

## Inject Service as Dependency

Now that `ApiTokenCacheService` is registered, you can inject it as a dependency of the `ApiService` service. Modify **ApiService.cs**

```csharp
public HttpClient _httpClient;
private readonly ApiTokenCacheService _apiTokenService;
 
public ApiService(HttpClient client, ApiTokenCacheService apiTokenCacheService)
{
    _httpClient = client;
    _apiTokenService = apiTokenCacheService;
}
```

Each time you make an API call, you must get the access token, either from cache or from the auth server, and set it as the bearer token for the `HttpClient` making the call. Your `GetContactsAsync()` method may look like the following:

```csharp
public async Task<List<Contact>> GetContactsAsync()
{
    var access_token = await _apiTokenService.GetAccessToken(
        "blazorcontacts-web",
        "blazorcontacts-api",
        "thisismyclientspecificsecret"
    );
    _httpClient.SetBearerToken(access_token);
 
    var response = await _httpClient.GetAsync("api/contacts");
    response.EnsureSuccessStatusCode();
 
    using var responseContent = await response.Content.ReadAsStreamAsync();
    return await JsonSerializer.DeserializeAsync<List<Contact>>(responseContent);
}
```

## The Bottom Line

In this tutorial, you learned how to cache access tokens for efficient API calls. You created a service class that checks for an existing token in the cache. If a valid token is not cached, your service fetched a new token from the authentication service and added the new token to the cache. In the next tutorial, you will learn an easy way to transparently manage access tokens in .NET Core 3.1 applications.

Source code for this project is [available on GitHub](https://github.com/bradwellsb/blazor-contacts/tree/tutorial-part3).

[Source](https://wellsb.com/csharp/aspnet/cache-identityserver4-access-token-blazor/)