Search code examples
c#asp.net-coredotnet-httpclientrefresh-tokenpolly

How do you get a new authentication token with a Typed Client in Polly?


I found this answer to be woefully incomplete:

Refresh Token using Polly with Typed Client

Same question as in this post, except the accepted answer seems critically flawed. Every time you make a request, you're going to get a 401 error, then you obtain an access token, attach it to the request and try again. It works, but you take an error on every single message.

The only solution I see is to set the default authentication header, but to do that, you need the HttpClient. So the original answer would need to do something like this:

services.AddHttpClient<TypedClient>()
    .AddPolicyHandler((provider, request) =>
    {
        return Policy.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.Unauthorized)
            .RetryAsync(1, (response, retryCount, context) =>
            {
                var httpClient = provider.GetRequiredService<HttpClient>();
                var authService = provider.GetRequiredService<AuthService>();
                httpClient.DefaultRequestHeaders.Authorization = authService.GetAccessToken());
            });
        });
    });

So the rub is, how do you get the current HttpClient in order to set the default access token so you don't call this handler every time you make a request? We only want to invoke this handler when the token has expired.


Solution

  • As I have stated in the comments I've already created two solutions:

    Both of the solutions are utilizing named clients. So, here I would focus only on that part which needs to be changed to use typed client.

    The good news is that you need to change only a tiny part of the solutions.

    Rewriting the custom exception based solution

    Only the following code needs to be changed from this:

    services.AddHttpClient("TestClient")
            .AddPolicyHandler((provider, _) => GetTokenRefresher(provider))
            .AddHttpMessageHandler<TokenFreshnessHandler>();
    

    to this:

    services.AddHttpClient<ITestClient, TestClient>
            .AddPolicyHandler((provider, _) => GetTokenRefresher(provider))
            .AddHttpMessageHandler<TokenFreshnessHandler>();
    

    The ITestClient and TestClient entities are independent from the rest of the solution.

    Rewriting the context based solution

    Only the following code needs to be changed

    services.AddHttpClient("TestClient")
        .AddPolicyHandler((sp, request) => GetTokenRefresher(sp, request))
        .AddHttpMessageHandler<TokenRetrievalHandler>()
    

    to this:

    services.AddHttpClient<ITestClient, TestClient>
        .AddPolicyHandler((sp, request) => GetTokenRefresher(sp, request))
        .AddHttpMessageHandler<TokenRetrievalHandler>()
    

    So the rub is, how do you get the current HttpClient in order to set the default access token so you don't call this handler every time you make a request? We only want to invoke this handler when the token has expired.

    With my proposed solution you don't need to access the HttpClient to set the default header, because the currently active access token is stored in a singleton class (TokenService). Inside the DelegatingHandler (TokenFreshnessHandler/TokenRetrievalHandler) you retrieve the latest, greatest token and set it on the HttpRequestMessage.