AuthorizationInterceptor 6.0.1
dotnet add package AuthorizationInterceptor --version 6.0.1
NuGet\Install-Package AuthorizationInterceptor -Version 6.0.1
<PackageReference Include="AuthorizationInterceptor" Version="6.0.1" />
<PackageVersion Include="AuthorizationInterceptor" Version="6.0.1" />
<PackageReference Include="AuthorizationInterceptor" />
paket add AuthorizationInterceptor --version 6.0.1
#r "nuget: AuthorizationInterceptor, 6.0.1"
#:package AuthorizationInterceptor@6.0.1
#addin nuget:?package=AuthorizationInterceptor&version=6.0.1
#tool nuget:?package=AuthorizationInterceptor&version=6.0.1
Authorization Interceptor
A lightweight .NET library that automatically manages HTTP authentication headers for HttpClient. When a request receives a 401 response, the interceptor re-authenticates and retries with fresh headers — no manual token management required.
Features
- Automatic retry on auth failure — intercepts 401 responses and retries with fresh headers
- OAuth2 refresh token support — reuse existing tokens via
RefreshTokenflow - Custom header support — return any key-value authorization headers
- Multiple cache backends — in-memory, distributed (Redis/NCache), or hybrid caching
- Distributed concurrency-safe — safe for multi-instance/Kubernetes deployments
- Extensible interceptor chain — compose your own caching and logging strategies
- Multi-target framework support — .NET 8+
Quick Start
Install the core package:
dotnet add package AuthorizationInterceptor
Step 1: Implement authentication logic
Create a class that implements IAuthenticationHandler:
public class TargetApiAuth : IAuthenticationHandler
{
private readonly HttpClient _client;
public TargetApiAuth(HttpClient client)
{
_client = client;
}
public async ValueTask<AuthorizationHeaders?> AuthenticateAsync(
AuthorizationHeaders? expiredHeaders, CancellationToken ct)
{
if (expiredHeaders == null)
{
// First login — request a fresh token
var response = await _client.PostAsync("auth", content: null, ct);
}
else
{
// Token expired — refresh it using the existing refresh token
// This step is only applicable to APIs integrating with OAuth refresh tokens
var refreshToken = expiredHeaders.OAuthHeaders!.RefreshToken;
var response = await _client.PostAsync($"refresh?refresh={refreshToken}", content: null, ct);
}
var json = await response.Content.ReadAsStringAsync(ct);
var tokens = JsonSerializer.Deserialize<UserTokens>(json)!;
return new OAuthHeaders(
accessToken: tokens.AccessToken,
tokenType: tokens.TokenType,
expiresIn: tokens.ExpiresIn,
refreshToken: tokens.RefreshToken,
refreshTokenExpiresIn: tokens.RefreshAccessTokenExpiresIn);
}
}
public record UserTokens(string AccessToken, string TokenType, int ExpiresIn, string RefreshToken, int RefreshAccessTokenExpiresIn);
Step 2: Register the handler
builder.Services.AddHttpClient("TargetApi")
.AddAuthorizationInterceptorHandler<TargetApiAuth>()
.ConfigureHttpClient(c => c.BaseAddress = new Uri("https://targetapi.com"));
That's it. Calls to this HttpClient will automatically retry with fresh authorization headers when a 401 is received.
Caching & Interceptors
By default, without any cache interceptor, a new access token is generated on every expiration. For production deployments, use one of the cache interceptors below to avoid redundant authentication calls.
Available Packages
| Package | Use case |
|---|---|
| AuthorizationInterceptor.Extensions.MemoryCache | Local in-memory caching — good for single-instance apps |
| AuthorizationInterceptor.Extensions.DistributedCache | Distributed caching (Redis, NCache, etc.) — for multi-instance deployments |
| AuthorizationInterceptor.Extensions.HybridCache | Memory + distributed cache combined — recommended for production |
Recommended configuration: Hybrid Cache
builder.Services.AddHttpClient("TargetApi")
.AddAuthorizationInterceptorHandler<TargetApiAuth>(options =>
{
options.UseHybridCacheInterceptor(); // memory → distributed → auth handler
})
.ConfigureHttpClient(c => c.BaseAddress = new Uri("https://targetapi.com"));
This uses an in-memory cache first (fastest), falls back to distributed cache, then calls the authentication handler only when no cached token exists. This ensures all instances share the same token and avoids redundant login calls.
Options & Customization
Retry on 403 as well as 401
Some APIs return 403 instead of 401 when tokens expire:
builder.Services.AddHttpClient("TargetApi")
.AddAuthorizationInterceptorHandler<TargetApiAuth>(options =>
{
options.UnauthenticatedPredicate = response =>
response.StatusCode is HttpStatusCode.Forbidden or HttpStatusCode.Unauthorized;
});
Multiple HttpClient instances with different auth data
When you need to pass extra dependencies into your authentication handler:
builder.Services.AddHttpClient("TargetApi")
.AddAuthorizationInterceptorHandler((sp) =>
ActivatorUtilities.CreateInstance<TargetApiAuth>(sp, someOtherDependency));
Per-request cache keys
When the same HttpClient is used for the same target API, but authorization headers must be cached separately by a request value, configure CacheKeyBuilder.
This is useful when a single integration can authenticate on behalf of different users, tenants, stores, organizations, or any other request-scoped identifier. The value returned by CacheKeyBuilder is appended to the cache key used by the configured cache interceptor.
Example using the authenticated user:
builder.Services.AddHttpClient("TargetApi")
.AddAuthorizationInterceptorHandler<TargetApiAuth>(options =>
{
options.UseHybridCacheInterceptor();
options.CacheKeyBuilder = accessor =>
accessor.HttpContext?.User.FindFirst("sub")?.Value;
});
Example using a route or query value:
builder.Services.AddHttpClient("TargetApi")
.AddAuthorizationInterceptorHandler<TargetApiAuth>(options =>
{
options.UseDistributedCacheInterceptor();
options.CacheKeyBuilder = accessor =>
{
var httpContext = accessor.HttpContext;
var storeId = httpContext?.Request.RouteValues["storeId"]?.ToString()
?? httpContext?.Request.Query["storeId"].ToString();
return string.IsNullOrWhiteSpace(storeId) ? null : storeId;
};
});
With this configuration, requests using the same HttpClient but different HttpContext.Request values will not share the same cached authorization headers.
If CacheKeyBuilder returns null or an empty value, the interceptor uses the default cache key for the HttpClient name.
Custom interceptors
Add custom logic steps to the interceptor chain:
builder.Services.AddHttpClient("TargetApi")
.AddAuthorizationInterceptorHandler<TargetApiAuth>(options =>
{
options.UseMemoryCacheInterceptor();
options.UseCustomInterceptor<MyLoggingInterceptor>();
});
Implement IAuthorizationInterceptor:
public class MyLoggingInterceptor : IAuthorizationInterceptor
{
public ValueTask<AuthorizationHeaders?> GetHeadersAsync(
string name, CancellationToken ct, string? cacheKeySuffix = null)
=> new(new AuthorizationHeaders());
public ValueTask UpdateHeadersAsync(
string name, AuthorizationHeaders? expiredHeaders,
AuthorizationHeaders? newHeaders, CancellationToken ct,
string? cacheKeySuffix = null)
{
// Log or transform headers between cache and auth handler
return default;
}
}
The interceptor chain becomes: MemoryCache → MyLoggingInterceptor → AuthHandler → MyLoggingInterceptor → MemoryCache. Build your own cache backend by targeting AuthorizationInterceptor.Extensions.Abstractions.
Sample Applications
Run a working demo with a mock API endpoint:
cd samples
dotnet run --project TargetApi # starts mock auth server on :5001
# in another terminal:
dotnet run --project SourceApi # calls the mock API with interceptor enabled
Source: Samples
License
This project is licensed under the MIT License. See LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
-
net8.0
-
net9.0
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated | |
|---|---|---|---|
| 6.0.1 | 118 | 6/16/2026 | |
| 6.0.0 | 93 | 6/15/2026 | |
| 5.4.1 | 11,551 | 2/12/2026 | |
| 5.4.0 | 125 | 1/19/2026 | |
| 5.2.0 | 14,388 | 9/18/2025 | |
| 5.0.1 | 223 | 9/12/2025 | |
| 5.0.0 | 247 | 3/18/2025 | |
| 5.0.0-preview-1 | 219 | 3/17/2025 | |
| 5.0.0-beta | 219 | 3/17/2025 | |
| 4.0.0 | 183 | 2/28/2025 | |
| 2.2.0 | 228 | 11/12/2024 | |
| 2.1.2 | 163 | 11/6/2024 | |
| 2.1.0 | 450 | 4/27/2024 | |
| 2.0.0 | 252 | 4/24/2024 | |
| 2.0.0-beta1 | 247 | 4/24/2024 | |
| 1.2.1-beta1 | 340 | 4/8/2024 | |
| 1.1.2-beta1 | 224 | 4/4/2024 | |
| 1.1.1-beta1 | 205 | 4/4/2024 | |
| 1.1.0-beta1 | 225 | 4/3/2024 | |
| 1.0.0 | 239 | 4/9/2024 |