diff --git a/README.md b/README.md index 0e8758d..68da41e 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,22 @@ services.AddHttpClient(x=> x.BaseAddress = new Uri("https://MySer .AddAccessToken(config => config.AudienceResolver = request => request.RequestUri.GetLeftPart(UriPartial.Authority)); ``` +### Enhanced Resilience + +The default rate-limit behaviour in Auth0.NET is suboptimal, as it uses random backoff rather than reading the rate limit headers returned by Auth0. +This package includes an additional `.AddAuth0RateLimitResilience()` extension that adds improved rate limit handling to the Auth0 clients. +If you're running into rate limit failures, I highly recommend adding this functionality: + +```csharp +services.AddAuth0ManagementClient() + .AddManagementAccessToken() + .AddAuth0RateLimitResilience(); +``` + +When a retry occurs, you should see a warning log similar to: + +`Resilience event occurred. EventName: '"OnRetry"', Source: '"IManagementConnection-RateLimitRetry"/""/"Retry"', Operation Key: 'null', Result: '429'` + ### Client Lifetimes Both the authentication and authorization clients are registered as singletons and are suitable for injection into any other lifetime. diff --git a/samples/Sample.AspNetCore/Sample.AspNetCore.csproj b/samples/Sample.AspNetCore/Sample.AspNetCore.csproj index 0863285..d486b33 100644 --- a/samples/Sample.AspNetCore/Sample.AspNetCore.csproj +++ b/samples/Sample.AspNetCore/Sample.AspNetCore.csproj @@ -8,7 +8,7 @@ - + diff --git a/samples/Sample.ConsoleApp/Sample.ConsoleApp.csproj b/samples/Sample.ConsoleApp/Sample.ConsoleApp.csproj index f4260c4..67849bc 100644 --- a/samples/Sample.ConsoleApp/Sample.ConsoleApp.csproj +++ b/samples/Sample.ConsoleApp/Sample.ConsoleApp.csproj @@ -9,9 +9,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Auth0Net.DependencyInjection/Auth0Extensions.cs b/src/Auth0Net.DependencyInjection/Auth0Extensions.cs index 8c4ed1c..c4b7fc6 100644 --- a/src/Auth0Net.DependencyInjection/Auth0Extensions.cs +++ b/src/Auth0Net.DependencyInjection/Auth0Extensions.cs @@ -92,6 +92,7 @@ private static IHttpClientBuilder AddAuth0AuthenticationClientInternal(this ISer services.AddSingleton(); return services.AddHttpClient() #if !NETFRAMEWORK + // TODO drop this code with the release of .NET 10 .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler() { diff --git a/src/Auth0Net.DependencyInjection/Auth0Net.DependencyInjection.csproj b/src/Auth0Net.DependencyInjection/Auth0Net.DependencyInjection.csproj index 7a4f2f0..0c72019 100644 --- a/src/Auth0Net.DependencyInjection/Auth0Net.DependencyInjection.csproj +++ b/src/Auth0Net.DependencyInjection/Auth0Net.DependencyInjection.csproj @@ -5,7 +5,7 @@ true enable enable - 5.0.0 + 5.1.0 Hawxy Dependency Injection, HttpClientFactory & ASP.NET Core extensions for Auth0.NET latest @@ -21,29 +21,29 @@ - - + + - - + + - - - + + + - - + + - - - + + + diff --git a/src/Auth0Net.DependencyInjection/HttpClient/Auth0ResilienceExtensions.cs b/src/Auth0Net.DependencyInjection/Auth0ResilienceExtensions.cs similarity index 76% rename from src/Auth0Net.DependencyInjection/HttpClient/Auth0ResilienceExtensions.cs rename to src/Auth0Net.DependencyInjection/Auth0ResilienceExtensions.cs index 18abec4..0b2eb44 100644 --- a/src/Auth0Net.DependencyInjection/HttpClient/Auth0ResilienceExtensions.cs +++ b/src/Auth0Net.DependencyInjection/Auth0ResilienceExtensions.cs @@ -1,11 +1,10 @@ #if NET8_0_OR_GREATER -using System.Diagnostics.CodeAnalysis; using System.Net; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Http.Resilience; using Polly; -namespace Auth0Net.DependencyInjection.HttpClient; +namespace Auth0Net.DependencyInjection; /// /// Extensions used to enhance Auth0 client resilience. @@ -13,18 +12,17 @@ namespace Auth0Net.DependencyInjection.HttpClient; public static class Auth0ResilienceExtensions { /// - /// Adds enhanced rate limiting support to the Auth0 Client. This API is experimental. + /// Adds enhanced rate limiting support to the Auth0 Client. /// - /// + /// The underlying + /// The max number of retry attempts to Auth0. Defaults to 10. /// - [Experimental("Auth0DIExperimental")] - public static IHttpResiliencePipelineBuilder AddAuth0RateLimitResilience(this IHttpClientBuilder builder) + public static IHttpResiliencePipelineBuilder AddAuth0RateLimitResilience(this IHttpClientBuilder builder, int maxRetryAttempts = 10) { return builder.AddResilienceHandler("RateLimitRetry", - static builder => + pipelineBuilder => { - // See: https://www.pollydocs.org/strategies/retry.html - builder.AddRetry(new HttpRetryStrategyOptions + pipelineBuilder.AddRetry(new HttpRetryStrategyOptions { // Disable the default handling of Retry-After header ShouldRetryAfterHeader = false, @@ -41,8 +39,7 @@ public static IHttpResiliencePipelineBuilder AddAuth0RateLimitResilience(this IH return new ValueTask((TimeSpan?)null); }, - - MaxRetryAttempts = 10, + MaxRetryAttempts = maxRetryAttempts, Delay = TimeSpan.FromSeconds(2) }); }); diff --git a/tests/Auth0Net.DependencyInjection.Tests/Auth0Net.DependencyInjection.Tests.csproj b/tests/Auth0Net.DependencyInjection.Tests/Auth0Net.DependencyInjection.Tests.csproj index e46aa2f..34deecd 100644 --- a/tests/Auth0Net.DependencyInjection.Tests/Auth0Net.DependencyInjection.Tests.csproj +++ b/tests/Auth0Net.DependencyInjection.Tests/Auth0Net.DependencyInjection.Tests.csproj @@ -9,8 +9,8 @@ - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/tests/Auth0Net.DependencyInjection.Tests/CacheTests.cs b/tests/Auth0Net.DependencyInjection.Tests/CacheTests.cs index b2767ac..177d66a 100644 --- a/tests/Auth0Net.DependencyInjection.Tests/CacheTests.cs +++ b/tests/Auth0Net.DependencyInjection.Tests/CacheTests.cs @@ -40,9 +40,9 @@ public async Task Cache_WorksAsExpected() var cache = new Auth0TokenCache(authClient, new FusionCacheTestProvider(), new NullLogger(), config); var key = "api://my-audience"; - var resFirst = await cache.GetTokenAsync(key); + var resFirst = await cache.GetTokenAsync(key, TestContext.Current.CancellationToken); Assert.Equal(accessTokenFirst, resFirst); - await Task.Delay(1000); + await Task.Delay(1000, TestContext.Current.CancellationToken); var accessTokenSecond = Guid.NewGuid().ToString(); @@ -54,7 +54,7 @@ public async Task Cache_WorksAsExpected() ExpiresIn = 1 }); - var resSecond = await cache.GetTokenAsync(key); + var resSecond = await cache.GetTokenAsync(key, TestContext.Current.CancellationToken); Assert.Equal(accessTokenSecond, resSecond); A.CallTo(() => authClient.GetTokenAsync(A.Ignored, A.Ignored))