Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,16 @@ dotnet add package AuthorizationInterceptor
When adding a new HttpClient, call the extension method `AddAuthorizationInterceptorHandler`, passing in the authentication class for the target API:
```csharp
services.AddHttpClient("TargetApi")
.AddAuthorizationInterceptorHandler<TargetApiAuthClass>()
.AddAuthorizationInterceptorHandler<TargetApiAuthClass>();
```

This will make the `TargetApi` HttpClient use the Authorization Interceptor handler to generate and manage authorization headers.
If you have different authentication options for the same integration within the same project, you can use the overload method by passing an implementation invoker for the authentication class, like this:
```csharp
services.AddHttpClient("TargetApi")
.AddAuthorizationInterceptorHandler((provider) => ActivatorUtilities.CreateInstance<TargetApiAuthClass>(provider, someOtherDependency));
```

> By default, the package will not use any interceptor to store authorization headers so the recommendation is to use at least [AuthorizationInterceptor.Extensions.MemoryCache](https://nuget.org/packages/AuthorizationInterceptor.Extensions.MemoryCache) package to store and manage the authorization headers lifecycle. Checkout [Interceptors section](#interceptors).
This will make the `TargetApi` HttpClient use the Authorization Interceptor handler to generate and manage authorization headers.

The `TargetApiAuthClass` must implement the `IAuthenticationHandler` interface, so that the package can perform the necessary dependency and know where and when to generate the authorization headers. An example implementation of the class would look like this:

Expand Down
46 changes: 46 additions & 0 deletions samples/SourceApi/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@
})
.ConfigureHttpClient(c => c.BaseAddress = new Uri("http://localhost:5121"));

builder.Services.AddHttpClient("TargetApiWithData1")
.AddAuthorizationInterceptorHandler(provider => ActivatorUtilities.CreateInstance<TargetApiWithDataAuthClass>(provider, new SomeData("data1")))
.ConfigureHttpClient(c => c.BaseAddress = new Uri("http://localhost:5121"));

builder.Services.AddHttpClient("TargetApiWithData2")
.AddAuthorizationInterceptorHandler(provider => ActivatorUtilities.CreateInstance<TargetApiWithDataAuthClass>(provider, new SomeData("data2")))
.ConfigureHttpClient(c => c.BaseAddress = new Uri("http://localhost:5121"));

var app = builder.Build();

if (app.Environment.IsDevelopment())
Expand Down Expand Up @@ -100,6 +108,18 @@
return await client.GetAsync("/data");
});

app.MapGet("/test/TargetApiWithData1", async (IHttpClientFactory factory) =>
{
var client = factory.CreateClient("TargetApiWithData1");
return await client.GetAsync("/data");
});

app.MapGet("/test/TargetApiWithData2", async (IHttpClientFactory factory) =>
{
var client = factory.CreateClient("TargetApiWithData2");
return await client.GetAsync("/data");
});

app.Run();

public class User
Expand Down Expand Up @@ -140,6 +160,30 @@ public TargetApiAuthClass(IHttpClientFactory httpClientFactory)
return new OAuthHeaders(user.AccessToken, user.TokenType, user.ExpiresIn, user.RefreshToken, user.RefreshTokenExpiresIn);
}
}

public class TargetApiWithDataAuthClass : IAuthenticationHandler
{
private readonly SomeData _data;
private readonly HttpClient _client;

public TargetApiWithDataAuthClass(SomeData data, IHttpClientFactory httpClientFactory)
{
_data = data;
_client = httpClientFactory.CreateClient("TargetApiAuth");
}

public async ValueTask<AuthorizationHeaders?> AuthenticateAsync(AuthorizationHeaders? expiredHeaders, CancellationToken cancellationToken)
{
var response = expiredHeaders != null && expiredHeaders.OAuthHeaders != null
? await _client.PostAsync($"refresh?refresh={expiredHeaders.OAuthHeaders?.RefreshToken}&data={_data.Data}", null)
: await _client.PostAsync($"auth?data={_data.Data}", null);

var content = await response.Content.ReadAsStringAsync();
var user = JsonSerializer.Deserialize<User>(content);
return new OAuthHeaders(user.AccessToken, user.TokenType, user.ExpiresIn, user.RefreshToken, user.RefreshTokenExpiresIn);
}
}

public class CustomInterceptor1 : IAuthorizationInterceptor
{
public ValueTask<AuthorizationHeaders?> GetHeadersAsync(string name, CancellationToken cancellationToken)
Expand Down Expand Up @@ -178,3 +222,5 @@ public ValueTask UpdateHeadersAsync(string name, AuthorizationHeaders? expiredHe
return ValueTask.CompletedTask;
}
}

public record SomeData(string Data);
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Hybrid" Version="9.8.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Hybrid" Version="9.9.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AuthorizationInterceptor.Extensions.Abstractions\AuthorizationInterceptor.Extensions.Abstractions.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,29 @@ public static IHttpClientBuilder AddAuthorizationInterceptorHandler<T>(this IHtt
return builder;
}

/// <summary>
/// Init a new authorization interceptor handler configuration for IHttpClientBuilder
/// </summary>
/// <param name="builder"><see cref="IHttpClientBuilder"/></param>
/// <param name="authHandlerImpl">A delegate that provides an implementation of <see cref="IAuthenticationHandler"/> using the given <see cref="IServiceProvider"/>.</param>
/// <param name="options">Configuration options of Authorization interceptor.</param>
/// <returns>Returns <see cref="IHttpClientBuilder"/></returns>
public static IHttpClientBuilder AddAuthorizationInterceptorHandler(this IHttpClientBuilder builder, Func<IServiceProvider, IAuthenticationHandler> authHandlerImpl, Action<AuthorizationInterceptorOptions>? options = null)
{
ArgumentNullException.ThrowIfNull(authHandlerImpl);

var optionsInstance = RequireOptions(options);
builder.AddHttpMessageHandler(provider => new AuthorizationInterceptorHandler(
builder.Name,
optionsInstance.UnauthenticatedPredicate,
authHandlerImpl.Invoke(provider),
CreateStrategy(provider, builder, optionsInstance.Interceptors),
provider.GetRequiredService<ILoggerFactory>()
));

return builder;
}

private static AuthorizationInterceptorOptions RequireOptions(Action<AuthorizationInterceptorOptions>? options)
{
var optionsInstance = new AuthorizationInterceptorOptions();
Expand Down
4 changes: 2 additions & 2 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<LangVersion>13</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>5.1.0</Version>
<Version>5.2.0</Version>
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
<Title>Authorization Interceptor</Title>
<Authors>Adolfok3</Authors>
Expand Down Expand Up @@ -42,7 +42,7 @@
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net9.0' And '$(IsCachingAbstractionsTargeted)' == 'true'">
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.9" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net8.0' And '$(IsCachingAbstractionsTargeted)' == 'true'">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@
Assert.Null(Record.Exception(act));
}



[Fact]
public void AddAuthorizationInterceptorHandler_WithOptions_ShouldBuildServiceProviderSucessfully()
{
Expand All @@ -62,4 +60,37 @@
// Assert
Assert.Null(Record.Exception(act));
}

[Fact]
public void AddAuthorizationInterceptorHandler_WithNullAuthHandler_ShouldThrowsArgumentNullException()
{
// Arrange
var services = new ServiceCollection();

// Act
IHttpClientBuilder act() => services.AddHttpClient("Test").AddAuthorizationInterceptorHandler(authHandlerImpl: null);

Check warning on line 71 in tests/AuthorizationInterceptor.Tests/Extensions/HttpClientBuilderExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / code-validation

Cannot convert null literal to non-nullable reference type.

Check warning on line 71 in tests/AuthorizationInterceptor.Tests/Extensions/HttpClientBuilderExtensionsTests.cs

View workflow job for this annotation

GitHub Actions / code-validation

Cannot convert null literal to non-nullable reference type.

// Assert
Assert.Throws<ArgumentNullException>((Func<IHttpClientBuilder>)act);
}

[Fact]
public void AddAuthorizationInterceptorHandler_WithOptionsAndAuthHandler_ShouldBuildServiceProviderSucessfully()
{
// Arrange
var services = new ServiceCollection();
services.AddHttpClient("Test")
.AddAuthorizationInterceptorHandler((provider) => ActivatorUtilities.CreateInstance<MockAuthorizationInterceptorAuthenticationHandler>(provider), options =>
{
options.UseCustomInterceptor<MockAuthorizationInterceptor>(func => func.AddSingleton<MockAuthorizationInterceptor>());
});

// Act
var provider = services.BuildServiceProvider();
var httpClientFactory = provider.GetRequiredService<IHttpClientFactory>();
var act = () => httpClientFactory.CreateClient("Test");

// Assert
Assert.Null(Record.Exception(act));
}
}
Loading