Skip to content

A thread-safe implementation of a DelegatingHandler that automatically refreshes the access token when the access token expires whilst **not** serializing all requests.

License

Notifications You must be signed in to change notification settings

RobThree/AutoRefreshTokenHttpMessageHandler

Repository files navigation

Logo AutoRefreshTokenHttpMessageHandler

Build Status Nuget version

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.

Quickstart

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

Custom service:

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

Add options to appsettings:

Client credentials:

{
  "MyClient": {
    "ClientId": "myclient",
    "ClientSecret": "clientsecretclientsecretclientsecret",
    "TokenEndpoint": "http://auth.myservice.com/realms/myapi/protocol/openid-connect/token"
  }
}

...or using username/password:

{
  "MyClient": {
    "ClientId": "myclient",
    "ClientSecret": "clientsecretclientsecretclientsecret",
    "Username": "admin",
    "Password": "mysup3rs4f3p455w0rd",
    "TokenEndpoint": "http://auth.myservice.com/realms/myapi/protocol/openid-connect/token"
  }
}

Attribution

Icon by Freepik

About

A thread-safe implementation of a DelegatingHandler that automatically refreshes the access token when the access token expires whilst **not** serializing all requests.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages