Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
20518b8f6a | |||
8249714c6b | |||
7e1e5c76b7 | |||
6fee2c5c9a | |||
0f1f900055 | |||
892101a84d | |||
3478750bc9 | |||
e7ebf0cf19 | |||
4c5b5aec66 | |||
6bebdf916c | |||
2e4a8b6d98 | |||
602076b669 | |||
9df5750f1c | |||
d1e217329a | |||
4e9119c8c7 |
@ -35,4 +35,4 @@ jobs:
|
|||||||
|
|
||||||
- name: Publish to NuGet.org
|
- name: Publish to NuGet.org
|
||||||
run: |
|
run: |
|
||||||
dotnet nuget push ./artifacts/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }}
|
dotnet nuget push ./artifacts/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }}
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Open Harbor
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
@ -0,0 +1,12 @@
|
|||||||
|
using System.Net.Http.Headers;
|
||||||
|
|
||||||
|
namespace OpenHarbor.JwtTokenManager.Abstractions;
|
||||||
|
|
||||||
|
public static class HttpClientExtensions
|
||||||
|
{
|
||||||
|
public static HttpClient SetJwtAccessToken(this HttpClient httpClient, JwtTokenManagerResult token)
|
||||||
|
{
|
||||||
|
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(token.TokenType.ToString(), token.AccessToken);
|
||||||
|
return httpClient;
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@ namespace OpenHarbor.JwtTokenManager.Abstractions;
|
|||||||
|
|
||||||
public class JwtTokenManagerResult
|
public class JwtTokenManagerResult
|
||||||
{
|
{
|
||||||
public required string Token { get; set; }
|
public required string AccessToken { get; set; }
|
||||||
public required TokenType TokenType { get; set; }
|
public required TokenType TokenType { get; set; }
|
||||||
public required DateTime ExpiresAt { get; set; }
|
public required DateTime ExpiresAt { get; set; }
|
||||||
public int ExpiresIn { get; set; }
|
public int ExpiresIn { get; set; }
|
||||||
|
@ -2,10 +2,28 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<IsAotCompatible>true</IsAotCompatible>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<Authors>Mathias Beaulieu-Duncan</Authors>
|
<Authors>Mathias Beaulieu-Duncan</Authors>
|
||||||
<PackageIconUrl>https://gravatar.com/avatar/9cecda5822fc5d4d2e61ec03da571b3d?size=256</PackageIconUrl>
|
<Company>Open Harbor</Company>
|
||||||
|
<PackageIcon>icon.png</PackageIcon>
|
||||||
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
|
<RepositoryUrl>https://git.openharbor.io/Open-Harbor/dotnet-jwt-token-manager</RepositoryUrl>
|
||||||
|
<RepositoryType>git</RepositoryType>
|
||||||
|
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||||
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
|
|
||||||
|
<DebugType>portable</DebugType>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
|
<IncludeSource>true</IncludeSource>
|
||||||
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="..\icon.png" Pack="true" PackagePath="" CopyToOutputDirectory="Always" />
|
||||||
|
<None Include="..\README.md" Pack="true" PackagePath="" CopyToOutputDirectory="Always" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
12
OpenHarbor.JwtTokenManager/JwtTokenManagerJsonContext.cs
Normal file
12
OpenHarbor.JwtTokenManager/JwtTokenManagerJsonContext.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace OpenHarbor.JwtTokenManager;
|
||||||
|
|
||||||
|
[JsonSourceGenerationOptions(
|
||||||
|
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower,
|
||||||
|
PropertyNameCaseInsensitive = true
|
||||||
|
)]
|
||||||
|
[JsonSerializable(typeof(JwtTokenResponse))]
|
||||||
|
public partial class JwtTokenManagerJsonContext : JsonSerializerContext
|
||||||
|
{
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using System.Text.Json;
|
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using OpenHarbor.JwtTokenManager.Abstractions;
|
using OpenHarbor.JwtTokenManager.Abstractions;
|
||||||
@ -10,15 +9,10 @@ namespace OpenHarbor.JwtTokenManager;
|
|||||||
public class JwtTokenManagerService(JwtTokenManagerOptions options, IHttpClientFactory httpClientFactory, ILogger<JwtTokenManagerService>? logger, IMemoryCache? memoryCache, JwtTokenManagerCacheOptions cacheOptions)
|
public class JwtTokenManagerService(JwtTokenManagerOptions options, IHttpClientFactory httpClientFactory, ILogger<JwtTokenManagerService>? logger, IMemoryCache? memoryCache, JwtTokenManagerCacheOptions cacheOptions)
|
||||||
: IJwtTokenManagerService
|
: IJwtTokenManagerService
|
||||||
{
|
{
|
||||||
|
private readonly HttpClient _httpClient = httpClientFactory.CreateClient();
|
||||||
private readonly TimeSpan _cacheExpirationOffset = TimeSpan.FromSeconds(cacheOptions.ExpirationOffset);
|
private readonly TimeSpan _cacheExpirationOffset = TimeSpan.FromSeconds(cacheOptions.ExpirationOffset);
|
||||||
private readonly string _scopes = string.Join(" ", options.Scopes);
|
private readonly string _scopes = string.Join(" ", options.Scopes);
|
||||||
|
|
||||||
private static readonly JsonSerializerOptions SnakeCaseOptions = new()
|
|
||||||
{
|
|
||||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
|
||||||
PropertyNameCaseInsensitive = true
|
|
||||||
};
|
|
||||||
|
|
||||||
public async Task<JwtTokenManagerResult> GetTokenAsync(CancellationToken cancellationToken = default)
|
public async Task<JwtTokenManagerResult> GetTokenAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (memoryCache != null)
|
if (memoryCache != null)
|
||||||
@ -29,34 +23,30 @@ public class JwtTokenManagerService(JwtTokenManagerOptions options, IHttpClientF
|
|||||||
return cachedToken;
|
return cachedToken;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var client = httpClientFactory.CreateClient();
|
var formContent = new FormUrlEncodedContent([
|
||||||
|
|
||||||
var formContent = new FormUrlEncodedContent(new[]
|
|
||||||
{
|
|
||||||
new KeyValuePair<string, string>("grant_type", "client_credentials"),
|
new KeyValuePair<string, string>("grant_type", "client_credentials"),
|
||||||
new KeyValuePair<string, string>("client_id", options.ClientId),
|
new KeyValuePair<string, string>("client_id", options.ClientId),
|
||||||
new KeyValuePair<string, string>("client_secret", options.ClientSecret),
|
new KeyValuePair<string, string>("client_secret", options.ClientSecret),
|
||||||
new KeyValuePair<string, string>("scopes", string.Join(" ", options.Scopes))
|
new KeyValuePair<string, string>("scopes", string.Join(" ", _scopes))
|
||||||
});
|
]);
|
||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, options.TokenEndpoint)
|
var request = new HttpRequestMessage(HttpMethod.Post, options.TokenEndpoint)
|
||||||
{
|
{
|
||||||
Content = formContent
|
Content = formContent
|
||||||
};
|
};
|
||||||
|
|
||||||
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||||
|
|
||||||
var response = await client.SendAsync(request, cancellationToken);
|
var response = await _httpClient.SendAsync(request, cancellationToken);
|
||||||
|
|
||||||
var t = await response.Content.ReadAsStringAsync(cancellationToken);
|
|
||||||
|
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
var tokenResponse = await response.Content.ReadFromJsonAsync<JwtTokenResponse>(SnakeCaseOptions, cancellationToken);
|
var tokenResponse = await response.Content.ReadFromJsonAsync(JwtTokenManagerJsonContext.Default.JwtTokenResponse, cancellationToken);
|
||||||
|
|
||||||
if (tokenResponse == null)
|
if (tokenResponse == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Failed to deserialize the response content.");
|
throw new InvalidOperationException("Failed to deserialize the token response content.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var parsedResult = Enum.TryParse<TokenType>(tokenResponse.TokenType, out var tokenType);
|
var parsedResult = Enum.TryParse<TokenType>(tokenResponse.TokenType, out var tokenType);
|
||||||
@ -71,7 +61,7 @@ public class JwtTokenManagerService(JwtTokenManagerOptions options, IHttpClientF
|
|||||||
var expiresAt = now.Add(expiration);
|
var expiresAt = now.Add(expiration);
|
||||||
var result = new JwtTokenManagerResult
|
var result = new JwtTokenManagerResult
|
||||||
{
|
{
|
||||||
Token = tokenResponse.AccessToken,
|
AccessToken = tokenResponse.AccessToken,
|
||||||
TokenType = tokenType,
|
TokenType = tokenType,
|
||||||
ExpiresAt = expiresAt,
|
ExpiresAt = expiresAt,
|
||||||
ExpiresIn = tokenResponse.ExpiresIn,
|
ExpiresIn = tokenResponse.ExpiresIn,
|
||||||
|
@ -2,12 +2,32 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<IsAotCompatible>true</IsAotCompatible>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<Authors>Mathias Beaulieu-Duncan</Authors>
|
<Authors>Mathias Beaulieu-Duncan</Authors>
|
||||||
<PackageIconUrl>https://gravatar.com/avatar/9cecda5822fc5d4d2e61ec03da571b3d?size=256</PackageIconUrl>
|
<Company>Open Harbor</Company>
|
||||||
|
<PackageIcon>icon.png</PackageIcon>
|
||||||
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
|
<RepositoryUrl>https://git.openharbor.io/Open-Harbor/dotnet-jwt-token-manager</RepositoryUrl>
|
||||||
|
<RepositoryType>git</RepositoryType>
|
||||||
|
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||||
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
|
|
||||||
|
<DebugType>portable</DebugType>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
|
<IncludeSource>true</IncludeSource>
|
||||||
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
|
|
||||||
|
<EnableJsonSourceGeneration>true</EnableJsonSourceGeneration>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="..\icon.png" Pack="true" PackagePath="" CopyToOutputDirectory="Always" />
|
||||||
|
<None Include="..\README.md" Pack="true" PackagePath="" CopyToOutputDirectory="Always" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@ -9,6 +10,7 @@ namespace OpenHarbor.JwtTokenManager;
|
|||||||
|
|
||||||
public static class ServiceCollectionExtensions
|
public static class ServiceCollectionExtensions
|
||||||
{
|
{
|
||||||
|
[RequiresDynamicCode("Not AoT safe signature. Will add one in the future.")]
|
||||||
public static IServiceCollection AddJwtTokenManager(
|
public static IServiceCollection AddJwtTokenManager(
|
||||||
this IServiceCollection services,
|
this IServiceCollection services,
|
||||||
IConfiguration configuration,
|
IConfiguration configuration,
|
||||||
@ -19,8 +21,7 @@ public static class ServiceCollectionExtensions
|
|||||||
throw new ArgumentNullException(nameof(configuration));
|
throw new ArgumentNullException(nameof(configuration));
|
||||||
if (string.IsNullOrWhiteSpace(sectionName))
|
if (string.IsNullOrWhiteSpace(sectionName))
|
||||||
throw new ArgumentException("Section name must be provided.", nameof(sectionName));
|
throw new ArgumentException("Section name must be provided.", nameof(sectionName));
|
||||||
|
|
||||||
// Configure JwtTokenManagerOptions from the section
|
|
||||||
services.Configure<JwtTokenManagerOptions>(configuration.GetSection(sectionName));
|
services.Configure<JwtTokenManagerOptions>(configuration.GetSection(sectionName));
|
||||||
|
|
||||||
// Apply the builder options
|
// Apply the builder options
|
||||||
@ -30,7 +31,7 @@ public static class ServiceCollectionExtensions
|
|||||||
// Register the service
|
// Register the service
|
||||||
services.AddSingleton<IJwtTokenManagerService>(provider =>
|
services.AddSingleton<IJwtTokenManagerService>(provider =>
|
||||||
{
|
{
|
||||||
var optionsMonitor = provider.GetRequiredService<Microsoft.Extensions.Options.IOptionsMonitor<JwtTokenManagerOptions>>();
|
var optionsMonitor = provider.GetRequiredService<IOptionsMonitor<JwtTokenManagerOptions>>();
|
||||||
var options = optionsMonitor.Get(Options.DefaultName);
|
var options = optionsMonitor.Get(Options.DefaultName);
|
||||||
|
|
||||||
// Apply additional configuration
|
// Apply additional configuration
|
||||||
|
69
README.md
Normal file
69
README.md
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# Lightweight library allowing to manage access token its lifetime with caching capability for services accounts with the oauth2 protocol
|
||||||
|
|
||||||
|
# Installing Nuget
|
||||||
|
|
||||||
|
> Install nuget package to your awesome project.
|
||||||
|
|
||||||
|
| Package Name | NuGet | NuGet Install |
|
||||||
|
|-----------------------------------------| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |----------------------------------------------------------------------------:|
|
||||||
|
| OpenHarbor.JwtTokenManager | [![NuGet](https://img.shields.io/nuget/v/OpenHarbor.JwtTokenManager.svg?style=flat-square&label=nuget)](https://www.nuget.org/packages/OpenHarbor.JwtTokenManager/) | ```PM> Install-Package OpenHarbor.JwtTokenManager``` |
|
||||||
|
| OpenHarbor.JwtTokenManager.Abstractions | [![NuGet](https://img.shields.io/nuget/v/OpenHarbor.JwtTokenManager.Abstractions.svg?style=flat-square&label=nuget)](https://www.nuget.org/packages/OpenHarbor.JwtTokenManager.Abstractions/) | ```PM> Install-Package OpenHarbor.JwtTokenManager.Abstractions``` |
|
||||||
|
|
||||||
|
# How to use
|
||||||
|
|
||||||
|
> appsettings.json
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"JwtTokenManager": {
|
||||||
|
"TokenEndpoint": "",
|
||||||
|
"ClientId": "",
|
||||||
|
"ClientSecret": "",
|
||||||
|
"Scopes": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> Program.cs
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
builder.Services.AddHttpClient();
|
||||||
|
builder.Services.AddMemoryCache(); // use the IMemoryCache provider you want
|
||||||
|
|
||||||
|
builder.Services.AddJwtTokenManager(builder.Configuration, "JwtTokenManager");
|
||||||
|
```
|
||||||
|
|
||||||
|
> Program.cs with deeper configurations
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
builder.Services.AddHttpClient();
|
||||||
|
|
||||||
|
builder.Services.AddJwtTokenManager(builder.Configuration, "JwtTokenManager", options =>
|
||||||
|
{
|
||||||
|
options.Cache(cacheOptions =>
|
||||||
|
{
|
||||||
|
cacheOptions.ExpirationOffset = 30; // 15 by default
|
||||||
|
},
|
||||||
|
// optional to configure your own IMemoryCache provider for the token management
|
||||||
|
provider => provider.GetRequiredService<IMemoryCache>());
|
||||||
|
|
||||||
|
options.Configuration(overrideConfiguration =>
|
||||||
|
{
|
||||||
|
overrideConfiguration.Scopes = ["offline_access"];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
> Use the JwtTokenManager
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class MyService(HttpClient httpClient, IJwtTokenManagerService jwtTokenManagerService)
|
||||||
|
{
|
||||||
|
public async Task DoActionAsync(CancellationToken cancellationToken) {
|
||||||
|
|
||||||
|
var tokenResult = await jwtTokenManagerService.GetTokenAsync(cancellationToken);
|
||||||
|
httpClient.SetJwtAccessToken(tokenResult);
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
Loading…
Reference in New Issue
Block a user