AuthorizationInterceptor 1.1.1-beta1
AuthorizationInterceptor 2.1.0
Additional DetailsPlease, consider updating the package to version 2.1.0 or later
See the version list below for details.
dotnet add package AuthorizationInterceptor --version 1.1.1-beta1
NuGet\Install-Package AuthorizationInterceptor -Version 1.1.1-beta1
<PackageReference Include="AuthorizationInterceptor" Version="1.1.1-beta1" />
paket add AuthorizationInterceptor --version 1.1.1-beta1
#r "nuget: AuthorizationInterceptor, 1.1.1-beta1"
// Install AuthorizationInterceptor as a Cake Addin
#addin nuget:?package=AuthorizationInterceptor&version=1.1.1-beta1&prerelease
// Install AuthorizationInterceptor as a Cake Tool
#tool nuget:?package=AuthorizationInterceptor&version=1.1.1-beta1&prerelease
<img src="./resources/icon.png" alt="image" width="auto" height="150">
Authorization Interceptor: A simple and lightweight .NET package designed to streamline HttpClient authenticated requests
What is Authorization Interceptor?
Authorization Interceptor is a custom handler added to your HttpClient builder. With it, there's no need to worry about the expiration time and management of the authorization headers of your requests. Offering the possibility to use OAuth2 with RefreshToken or custom headers, whenever a request is sent and its response is a status code 401 (Unauthorized), the Interceptor will update the authorization headers and resend the same request with the updated authorization headers.
Authorization Interceptor uses MemoryCache to store authorization headers according to their expiration time, thus, it's not necessary to login or generate authorization every time you need to send a request to the API.
Authorization Interceptor can also share the same authorization headers with other instances of the application (if your application runs in a dockerized environment with Kubernetes) using the idea of distributed cache, avoiding concurrency among instances to log in or generate authorization headers with the API by reusing the authorization generated by the primary instance.
Getting started
Installation
Authorization Interceptor is installed from NuGet. Just run the following command in package manager console:
PM> Install-Package AuthorizationInterceptor
Or from the .NET CLI as:
dotnet add package AuthorizationInterceptor
Setup
When adding a new HttpClient, call the extension method AddAuthorizationInterceptorHandler
, passing in the authentication class for the target API:
services.AddHttpClient("TargetApi")
.AddAuthorizationInterceptorHandler<TargetApiAuthClass>()
Finally, invoke the method BuildAuthorizationInterceptor
:
services.AddHttpClient("TargetApi")
.AddAuthorizationInterceptorHandler<TargetApiAuthClass>()
.BuildAuthorizationInterceptor()
.ConfigureHttpClient(opt => opt.BaseAddress = new Uri("https://targetapi.com"));
This will make the TargetApi
HttpClient use the Authorization Interceptor handler to generate, manage, and store authorization headers. By default, the package will use MemoryCache for storage.
The TargetApiAuthClass
must implement the IAuthenticationHandler
interface, so that the package can perform the necessary dependency injection and know where and when to generate the authorization. An example implementation of the class would look like this:
public class TargetApiAuthClass : IAuthenticationHandler
{
public async Task<AuthorizationEntry> AuthenticateAsync()
{
//The login call to the target API should be placed here, and it should return the authorization headers.
var authorization = LoginToMyTargetApi();
return new OAuthEntry(authorization.AccessToken, authorization.TokenType, authorization.ExpiresIn, authorization.RefreshToken);
}
public async Task<AuthorizationEntry> UnauthenticateAsync(AuthorizationEntry? entries)
{
//This method is called when the interceptor captures a response with a status code of 401. It is most commonly used for integrations with APIs that use RefreshToken. If the target API does not have the refresh token functionality, you should implement the same call as in the `AuthenticateAsync` method.
var newHeaders = LoginWithRefreshToMyTargetApi(entries.RefreshToken);
return new OAuthEntry(newHeaders.AccessToken, newHeaders.TokenType, newHeaders.ExpiresIn, newHeaders.RefreshToken);
}
}
In the example above, we showed the TargetApiAuthClass
class, which must implement the authentication methods with the target API. Initially, the authorization headers do not exist, so the package will call the AuthenticateAsync
method just once and will store the authorization in memory cache, always consulting it from there. However, if there is a response with status code 401 (unauthorized), the package will call the UnauthenticateAsync
method, passing the old/expired authorization and will return the new authorization.
Note that in the
AuthenticateAsync
andUnauthenticateAsync
methods the return type isAuthorizationEntry
but in the example above anOAuthEntry
is returned, because in this example we are assuming that the target API uses the OAuth2 authentication mechanism. However, if your target API does not have this functionality you can return a new object of typeAuthorizationEntry
that inherits from a classDictionary<string, string>
. In practice, it would look like this:
public async Task<AuthorizationEntry> AuthenticateAsync()
{
return new AuthorizationEntry
{
{ "MyCustomAuthorizationHeader", "MytokenValue" },
{ "SomeOtherAuthorizationHeader", "OtherValue" }
};
}
Custom Options
Assuming that your target API is legacy and it returns not only the status code 401 (unauthorized) for requests without authorization or with expired authorization but also returns 403 (forbidden). For this situation, there is a property in the options class called UnauthenticatedPredicate
that customizes the type of predicate the package should evaluate for unauthorized requests.
In the extension method AddAuthorizationInterceptorHandler
, pass this custom configuration in the following way:
services.AddHttpClient("TargetApi")
.AddAuthorizationInterceptorHandler<TargetApiAuthClass>(options =>
{
opts.UnauthenticatedPredicate = response => response.StatusCode == System.Net.HttpStatusCode.Forbidden ||
response.StatusCode == System.Net.HttpStatusCode.Unauthorized;
})
Now, whenever there is a response with status code 401 or 403, the package will consider the authorization to be expired and will perform a new authentication.
Note that within the options class, there is a property
DisableMemoryCache
that, if set to true, the package will not use MemoryCache for storing authorization headers. We recommend leaving this property as false to avoid unnecessary authentication calls.
Interceptors
By default, the only configured interceptor is MemoryCache, but when we have a scalable application running in a dockerized environment with Kubernetes, there can be more than one instance of the same application. This can lead to authentication concurrency issues between instances and divergent authorization headers among them. To solve this, it is recommended to use a custom interceptor that implements the idea of distributed cache. Therefore, in addition to saving the authorization headers in memory, the application will also save them in a distributed cache so that other instances of the application can reuse the authorization already generated by a primary instance. This avoids multiple authentication calls and divergent authorizations.
In practice, simply choose from the available libraries for the package that implement DistributedCache and install it in your project. So far, we have the following integrated packages:
After selecting and installing the distributed cache package, in the constructor of the Authorization Interceptor, call the respective extension method. In the example below, we will use the Redis integration to implement the distributed cache.
services.AddHttpClient("TargetApi")
.AddAuthorizationInterceptorHandler<TargetApiAuthClass>()
.AddStackExchangeRedisCache(options =>
{
options.Configuration = "MyRedisConStr";
options.InstanceName = "SampleInstance";
})
.BuildAuthorizationInterceptor()
.ConfigureHttpClient(opt => opt.BaseAddress = new Uri("https://targetapi.com"));
With this configuration, the Authorization Interceptor will create a chain of responsibility in this sequence: MemoryCache > DistributedCache > AuthenticationHandler > DistributedCache > MemoryCache
.
Custom Interceptors
It's possible to add custom interceptors in the chain of interceptors. Create your Interceptor class and have it inherit from the abstract class AuthorizationInterceptorBase
. After that, add it to the constructor of the Authorization Interceptor through the method AddCustom<T>
, e.g.:
services.AddHttpClient("TargetApi")
.AddAuthorizationInterceptorHandler<TargetApiAuthClass>()
.AddCustom<MyCustomInterceptor>()
.BuildAuthorizationInterceptor()
.ConfigureHttpClient(opt => opt.BaseAddress = new Uri("https://targetapi.com"));
MyCustomInterceptor
class:
public class MyCustomInterceptor : AuthorizationInterceptorBase
{
public MyCustomInterceptor(IAuthenticationHandler authenticationHandler, ILogger<AuthorizationInterceptorBase> logger, IAuthorizationInterceptor? nextInterceptor = null)
: base("MyCustomInterceptor", authenticationHandler, logger, nextInterceptor)
{
}
protected override Task<AuthorizationEntry> OnGetHeadersAsync()
{
//Do something and call base method to get headers from next Interceptor
return base.OnGetHeadersAsync();
}
protected override Task<AuthorizationEntry> OnUpdateHeadersAsync(AuthorizationEntry expiredEntries)
{
//Do something with expired headers and call base method to get new headers from next Interceptor
return base.OnUpdateHeadersAsync(expiredEntries);
}
}
With this configuration, the Authorization Interceptor will create a chain of responsibility in this sequence: MemoryCache > MyCustomInterceptor > AuthenticationHandler > MyCustomInterceptor > MemoryCache
.
Note that the constructor of the
MyCustomInterceptor
class needs to receive some necessary parameters for dependency injection to work among interceptors.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 is compatible. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. 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. |
-
net5.0
- Microsoft.Extensions.Caching.Memory (>= 5.0.0)
- Microsoft.Extensions.Http (>= 5.0.0)
-
net6.0
- Microsoft.Extensions.Caching.Memory (>= 6.0.1)
- Microsoft.Extensions.Http (>= 6.0.0)
-
net7.0
- Microsoft.Extensions.Caching.Memory (>= 7.0.0)
- Microsoft.Extensions.Http (>= 7.0.0)
-
net8.0
- Microsoft.Extensions.Caching.Memory (>= 8.0.0)
- Microsoft.Extensions.Http (>= 8.0.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 | |
---|---|---|---|
2.1.0 | 98 | 4/27/2024 | |
2.0.0 | 109 | 4/24/2024 | |
2.0.0-beta1 | 90 | 4/24/2024 | |
1.2.1-beta1 | 165 | 4/8/2024 | |
1.1.2-beta1 | 87 | 4/4/2024 | |
1.1.1-beta1 | 87 | 4/4/2024 | |
1.1.0-beta1 | 85 | 4/3/2024 | |
1.0.0 | 103 | 4/9/2024 | |
1.0.0-beta1 | 85 | 4/2/2024 |