Logo AutoRefreshTokenHttpMessageHandler
This is a thread-safe implementation of a DelegatingHandler
, available as Nuget package, that automatically refreshes the access token when the access token expires whilst not serializing all requests. Most implementations use a lock internally which essentially makes all async actions synchronous again. This implementation only blocks during the actual refresh. Inspired by Bryan Helms' Thread-Safe Auth Token Store Using ConcurrentDictionary and AsyncLazy.
var builder = WebApplication.CreateBuilder(args); builder.Services.Configure<TokenOptions>(configuration.GetRequiredSection("MyClient")) .AddTransient<TokenDelegatingHandler>() .AddHttpClient<ITokenAuthenticationService, TokenAuthenticationService>() .ConfigureHttpClient(c => c.BaseAddress = new Uri("http://auth.myservice.com")).Services
builder.Services.AddHttpClient<MyService>() .ConfigureHttpClient(c => c.BaseAddress = new Uri("http://myservice.com")) .AddHttpMessageHandler<TokenDelegatingHandler>(); public class MyService(HttpClient client) { public Task<IEnumerable<MyFoo>> GetFoos() => client.GetFromJsonAsync<IEnumerable<Foo>>("/api/v1/foos"); }
...or using Refit:
// ...or Refit: builder.Services.AddRefitClient<IMyService>() .ConfigureHttpClient(c => c.BaseAddress = new Uri("http://myservice.com")) .AddHttpMessageHandler<TokenDelegatingHandler>(); public interface IMyService { [Get("/api/v1/foos")] Task<IEnumerable<Foo>> GetFooss(); }
Adding Polly to the mix:
builder.Services.AddHttpClient<MyService>() .ConfigureHttpClient(c => c.BaseAddress = new Uri("http://myservice.com")) .AddHttpMessageHandler<TokenDelegatingHandler>(); .AddPolicyHandler(HttpPolicyExtensions .HandleTransientHttpError() .WaitAndRetryAsync(Backoff.DecorrelatedJitterBackoffV2(TimeSpan.FromSeconds(1), 3)) ).Services
{ "MyClient": { "ClientId": "myclient", "ClientSecret": "clientsecretclientsecretclientsecret", "TokenEndpoint": "http://auth.myservice.com/realms/myapi/protocol/openid-connect/token" } }
{ "MyClient": { "ClientId": "myclient", "ClientSecret": "clientsecretclientsecretclientsecret", "Username": "admin", "Password": "mysup3rs4f3p455w0rd", "TokenEndpoint": "http://auth.myservice.com/realms/myapi/protocol/openid-connect/token" } }
Icon by Freepik