Thread Safe HttpService With JWT Refresh

One of the most common tasks for backend developers is integrating with third party APIs. And most of the time we end up solving the same problems over and over again, like automatically refreshing the auth token, which eventually expires.

Let’s consider the following scenario. There is a third party API at https://denispavlov.net, which requires authentication. We know that there is a GET /auth endpoint which returns an access token valid for 30 minutes. And we’re interested in a resource GET /api, which requires that token to be sent in request header as Authorization: Bearer {token}.

To refresh the JWT, I never like to rely on token expiration timestamp coming from a third party server, even when it’s available, because it’s not reliable imho (server clocks might be wrong, etc). Instead, I prefer to wait for the first 401 Unauthorized response to be my indicator that it’s time for token renewal. Let’s consider the following code to accomplish this scenario.

using System.Net;

public class HttpService
{
    private const string _authUrl = "https://denispavlov.net/auth";
    private readonly HttpClient _client;
    private readonly string _authHeader = HttpRequestHeader.Authorization.ToString();
    private static string _jwt = string.Empty;

    public HttpService(HttpClient client)
    {
        _client = client;

        SetJwt();
    }

    private void SetJwt()
    {
        using var request = new HttpRequestMessage(HttpMethod.Get, _authUrl);
        using var response = _client.Send(request);
        using var stream = response.Content.ReadAsStream();
        using var reader = new StreamReader(stream);
        _jwt = reader.ReadToEnd();
    }

    public async Task<string> GetContentAsync(string url)
    {
        var response = await SendAsync(HttpMethod.Get, url);

        if (!response.IsSuccessStatusCode)
        {
            SetJwt();

            response = await SendAsync(HttpMethod.Get, url);
        }

        return await response.Content.ReadAsStringAsync();
    }

    private async Task<HttpResponseMessage> SendAsync(HttpMethod method, string url)
    {
        var request = new HttpRequestMessage(method, url);
        request.Headers.Add(_authHeader, $"Bearer {_jwt}");
        return await _client.SendAsync(request);
    }
}

At the first glance, it looks fine. We inject HttpClient using HttpClientFactory Typed Client pattern, we get the access token and store it in a field. When consumer of this class calls GetContentAsync and first response isn’t successful, refresh access token and retry.

This solution will work in a single threaded application where requests come in one at a time. Unfortunately, that is not the reality for most production applications, and this code may have some issues when handling many concurrent requests. The problem is that our token refresh method is not thread safe, therefore you may end up with multiple threads trying to refresh the token simultaneously (potentially making extra unnecessary calls to the third party API, or worse, getting throttled because the GET /auth may have request rate limit). Let’s consider a better thread-safe solution.

Important Observation #1: The SetJwt method has been modified to lock the critical section of code to ensures that only one thread can enter at a time. Hence, avoiding potential simultaneous calls to refresh the token.

Important Observation #2: The two if statements if (string.IsNullOrEmpty(_jwt)) before and after the lock. This ensures that if there is a race condition where the first thread just executed line 28 and the second thread already entered the first if statement on line 21, but hasn’t entered the lock, there is a secondary check to avoid the second renewal.

Important Observation #3: The new private ExecuteWithRetriesAsync method is a wrapper for all API calls that require access token. It utilizes Polly library that is now included in .NET to automatically refresh JWT and retry the failed request once. Line 53 has an important if statement to ensure that we’re clearing out the expired JWT, in case another thread refreshed it.

NOTE: If you anticipate very low traffic on this service, you may move the SetJwt call from the constructor to the beginning of GetContentAsync, to avoid a situation where the token expired before you got a chance to use it. Make sure any other methods you want to expose that invoke HttpClient are wrapped in ExecuteWithRetriesAsync as done in my GetContentAsync example.


Leave a Reply

Your email address will not be published. Required fields are marked *