Handle token retrieval while querying an API
In our daily job, we often have to query secure REST APIs that require our HTTP requests to have a valid access token in their Authorization header. Of course, many APIs come with an SDK that makes the job easier for us as it directly takes care of retrieving a token and sending the authenticated HTTP requests. However, it is not always the case and knowing how to implement that using HttpClient, IMemoryCache, and DelegatingHandler can become pretty useful.
Context
Let's imagine we have a very simple API that contains the following routes :
The POST /login
route returns an AuthResponse
that contains the necessary Bearer token to call the 2 protected routes GET /users
and PUT /users/{username}
.
We want to implement an IUserService that has 2 methods:
GetAllUsers
to retrieve the list of users that will use theGET /users
routeUpdateUser
to update a user that will use thePUT /users/{username}
route
public interface IUserService
{
Task<IReadOnlyCollection<User>> GetAllUsers();
Task UpdateUser(User userToUpdate);
}
Each of these methods needs to retrieve a valid token from the POST /login
route and set the Authorization header with this token in the HTTP request to each of the protected routes.
The following code shows how to retrieve the token:
var body = new { login = "login", password = "password" };
var response = await _httpClient.PostAsync("login", new StringContent(JsonConvert.SerializeObject(body)));
var authResponse = await response.Content.ReadAsAsync<AuthResponse>();
where AuthResponse
is a class we defined to map the response of the POST /login
route
public class AuthResponse
{
public string Token { get; set; }
public DateTime Expiration { get; set; }
}
So now we have the code to retrieve the token, how do we use it to implement our IUserService
?
Retrieve the token from a private method
The easiest way to do that is to create a private method in UserService
that returns this token and to call it from GetAllUsers
and UpdateUser
. That would give us something like that :
public class UserService : IUserService
{
private readonly HttpClient _httpClient;
public UserService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<IReadOnlyCollection<User>> GetAllUsers()
{
var token = await RetrieveToken();
var request = new HttpRequestMessage(HttpMethod.Get, "user");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsAsync<IReadOnlyCollection<User>>();
}
public async Task UpdateUser(User userToUpdate)
{
var token = await RetrieveToken();
var request = new HttpRequestMessage(HttpMethod.Put, $"user/{userToUpdate.Name}");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
request.Content = new StringContent(JsonConvert.SerializeObject(userToUpdate));
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
}
private async Task<string> RetrieveToken()
{
var body = new { login = "login", password = "password" };
var response = await _httpClient.PostAsync("login", new StringContent(JsonConvert.SerializeObject(body)));
var authResponse = await response.Content.ReadAsAsync<AuthResponse>();
return authResponse.Token;
}
}
There are two main problems with this way of doing things:
- We have some code duplication as we are calling the
RetrieveToken
in each of our methods calling the API. That could be okay here as we only have 2 methods calling the API but that can quickly be problematic if we start to have more methods and repeat the call toRetrieveToken
in each method. - For each call to an authenticated route of the API, we are making a call to the
login
route even if our token from a previous call is probably still valid.
Use a dedicated service to retrieve the token and save it for future calls
Although it's not necessary at this point, it can be interesting to move the code of our private method RetrieveToken
into a separate service UserApiAuthenticationService
that will be injected in UserService
. That way, if the authentication method changes someday, UserService
implementation won't change. Moreover, we won't mess with the same HttpClient
for authentication and other calls.
public class UserApiAuthenticationService : IUserApiAuthenticationService
{
private readonly IMemoryCache _memoryCache;
private readonly HttpClient _httpClient;
public UserApiAuthenticationService(HttpClient httpClient, IMemoryCache memoryCache)
{
_httpClient = httpClient;
_memoryCache = memoryCache;
}
public async Task<string> RetrieveToken()
{
DateTime expirationDate;
if (!_memoryCache.TryGetValue("Token", out string token))
{
var body = new { login = "login", password = "password" };
var response = await _httpClient.PostAsync("login", new StringContent(JsonConvert.SerializeObject(body)));
(token, expirationDate) = await response.Content.ReadAsAsync<AuthResponse>();
_memoryCache.Set("Token", token, new DateTimeOffset(expirationDate));
}
return token;
}
}
To avoid requesting always the same token to the API, we added a line to store the token in the memory cache and a line to check if the token is already in the cache before querying the API.
We could also have used a class as a singleton to store the token and its expiration date, but the built-in IMemoryCache
of ASP.NET Core is more convenient and handle the expiration of the token for us by removing it from the cache when the date is passed. You can find more about cache memory in ASP.NET Core here.
Use a Delegating handler to directly set the token in the HttpClient request
Handling the token retrieval in a separate service is nice but that does not solve the issue of duplicated code. Even if the RetrieveToken
method is now part of UserApiAuthenticationService
, each method of UserService
will still call RetrieveToken
. Moreover setting the token on each request should not be a concern of UserService
.
That's where come delegating handlers. A delegating handler is quite similar to an ASP.NET Core middleware but instead of applying some processing on an incoming request and its response, it does so on an outgoing request and its response. In concrete terms, you use a delegating handler to apply something (logging, authentication, caching ...) to HTTP requests you make to an API using an HttpClient
. To learn more about delegating handlers there is a nice article from Steve Gordon on the topic.
A custom delegating handler is exactly what we need: a piece of code that all our HTTP requests from UserService
will go through and where we will be able to set the token on the authentication header of each request. Here is the code of our custom delegating handler:
public class UserApiAuthenticationHandler : DelegatingHandler
{
private readonly IUserApiAuthenticationService _authenticationService;
public UserApiAuthenticationHandler(IUserApiAuthenticationService authenticationService)
{
_authenticationService = authenticationService;
}
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationTokencancellationToken)
{
var token = await _authenticationService.RetrieveToken();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
return await base.SendAsync(request, cancellationToken);
}
}
That's it, we don't need anymore to handle token retrieval on UserService which becomes simpler:
public class UserService :
{
private readonly HttpClient _httpClient;
public UserService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<IReadOnlyCollection<User>> GetAllUsers()
{
var response = await _httpClient.GetAsync(new Uri("user"));
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsAsync<IReadOnlyCollection<User>>();
}
public async Task UpdateUser(User userToUpdate)
{
var content = new StringContent(JsonConvert.SerializeObject(userToUpdate));
var response = await _httpClient.PutAsync($"user/{userToUpdate.Name}", content);
response.EnsureSuccessStatusCode();
}
}
To finish we just have to specify in the Startup.cs
on which HttpClient to apply the delegating handler we have just created.
public void ConfigureServices(IServiceCollectionservices)
{
services.AddMemoryCache();
services.AddHttpClient<IUserApiAuthenticationService, UserApiAuthenticationService>()
.ConfigureHttpClient(c => c.BaseAddress ="http://urltotheuserapi.com");
services.AddTransient<UserApiAuthenticationHandler>();
services.AddHttpClient<UserService, UserService>()
.ConfigureHttpClient(c => c.BaseAddress ="http://urltotheuserapi.com")
.AddHttpMessageHandler<UserApiAuthenticationHanler>();
}
To conclude
To summarize, we have put the code that retrieves a token in a separate dedicated service that caches the token until it expires. And we have created a custom delegating handler that calls this service and sets the retrieved token on the authentication header of each HTTP request to the API.
Clean up your local git branches.
When working on a git repository, I often have to manually delete old local branches that I don't use anymore. That's not a huge waste of time but still, that's something I have to do quite often so I decided to automate that.
Coming across Gitpod
The other day when I was looking for a way to automate my development environment setup, I came across Gitpod. Not really what I was looking for but I discovered an awesome tool for working on open source projects.