If you have a service, which calls another service, chances are that you need to retrieve and store a token for authentication. In this blog post we will take a look at an approach on how to handle this, with a single class. Let’s start with the code (for those of you who just want to copy+paste) and then look at the separate parts in details afterwards.
public class TokenProvider
{
private const string tokenKey = "my_token";
private readonly HttpClient _client;
private readonly IMemoryCache _cache;
public TokenProvider(
HttpClient client,
IMemoryCache cache)
{
_client = client;
_cache = cache;
}
public async Task<string> GetToken()
{
TokenResponse token;
if (!_cache.TryGetValue(tokenKey, out var cacheEntry))
{
token = await GetFreshToken();
SaveToCache(token);
return token.AccessToken;
}
token = cacheEntry as TokenResponse;
return token.AccessToken;
}
private void SaveToCache(TokenResponse token)
{
var options = new MemoryCacheEntryOptions();
// We want the token to expire in cache before it
// actually expires, to avoid getting a token, which
// then expires after a few seconds.
options.SetAbsoluteExpiration(
TimeSpan.FromSeconds(token.ExpiresIn - 60));
_cache.Set(tokenKey, token, options);
}
private async Task<TokenResponse> GetFreshToken()
{
var tokenRequest = new PasswordTokenRequest()
{
UserName = "john_doe",
Password = "qwerty123",
Address = "https://token-provider.com/get-token",
ClientId = "client_id",
ClientSecret = "client_secret"
};
var token = await _client
.RequestPasswordTokenAsync(tokenRequest);
return token;
}
}
The first thing that is worth mentioning, is the TokenRespons
e type, which comes from the IdentityModel.Client
namespace. This also provides for the TokenRequest
type and the RequestPasswordTokenAsync
extension method. You can read more about how it works in the Identity Model documentation. In short, it allows us easy access to a bunch of things, we would otherwise have to build ourselves.
It might also be worth looking at the line if
(!_cache.TryGetValue(tokenKey, out
var
cacheEntry))
. This means that if it fails to get a token from the cache, it will run the code block from the if statement.
The token request may not be exactly what you need. There are many different types of requests, all described in the documentation. Find the one that matches your need.
Finally, you need to configure the TokenProvider as a singleton in the Startup.cs file (or Program.cs, if you are using minimal api).
services.AddSingleton<TokenProvider>();
Regarding the dependencies on IHttpClient
and IMemoryClient
: I didn’t seem to need them configured. But if it fails, just add both of them to services as scoped.