90 lines
3.4 KiB
C#
90 lines
3.4 KiB
C#
|
using System.Net.Http.Headers;
|
||
|
using System.Net.Http.Json;
|
||
|
using System.Text.Json;
|
||
|
using Microsoft.Extensions.Caching.Memory;
|
||
|
using Microsoft.Extensions.Logging;
|
||
|
using OpenHarbor.JwtTokenManager.Abstractions;
|
||
|
|
||
|
namespace OpenHarbor.JwtTokenManager;
|
||
|
|
||
|
public class JwtTokenManagerService(JwtTokenManagerOptions options, IHttpClientFactory httpClientFactory, ILogger<JwtTokenManagerService>? logger, IMemoryCache? memoryCache, JwtTokenManagerCacheOptions cacheOptions)
|
||
|
: IJwtTokenManagerService
|
||
|
{
|
||
|
private readonly TimeSpan _cacheExpirationOffset = TimeSpan.FromSeconds(cacheOptions.ExpirationOffset);
|
||
|
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)
|
||
|
{
|
||
|
if (memoryCache != null)
|
||
|
{
|
||
|
var memoryGetValueResult = memoryCache.TryGetValue(cacheOptions.CacheKey, out JwtTokenManagerResult? cachedToken);
|
||
|
if (memoryGetValueResult && null != cachedToken)
|
||
|
{
|
||
|
return cachedToken;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var client = httpClientFactory.CreateClient();
|
||
|
|
||
|
var formContent = new FormUrlEncodedContent(new[]
|
||
|
{
|
||
|
new KeyValuePair<string, string>("grant_type", "client_credentials"),
|
||
|
new KeyValuePair<string, string>("client_id", options.ClientId),
|
||
|
new KeyValuePair<string, string>("client_secret", options.ClientSecret),
|
||
|
new KeyValuePair<string, string>("scopes", string.Join(" ", options.Scopes))
|
||
|
});
|
||
|
|
||
|
var request = new HttpRequestMessage(HttpMethod.Post, options.TokenEndpoint)
|
||
|
{
|
||
|
Content = formContent
|
||
|
};
|
||
|
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||
|
|
||
|
var response = await client.SendAsync(request, cancellationToken);
|
||
|
|
||
|
var t = await response.Content.ReadAsStringAsync(cancellationToken);
|
||
|
|
||
|
response.EnsureSuccessStatusCode();
|
||
|
|
||
|
var tokenResponse = await response.Content.ReadFromJsonAsync<JwtTokenResponse>(SnakeCaseOptions, cancellationToken);
|
||
|
|
||
|
if (tokenResponse == null)
|
||
|
{
|
||
|
throw new InvalidOperationException("Failed to deserialize the response content.");
|
||
|
}
|
||
|
|
||
|
var parsedResult = Enum.TryParse<TokenType>(tokenResponse.TokenType, out var tokenType);
|
||
|
|
||
|
if (parsedResult == false)
|
||
|
{
|
||
|
throw new InvalidOperationException($"Unsupported token type: {tokenResponse.TokenType}");
|
||
|
}
|
||
|
|
||
|
var now = DateTime.UtcNow;
|
||
|
var expiration = TimeSpan.FromSeconds(tokenResponse.ExpiresIn);
|
||
|
var expiresAt = now.Add(expiration);
|
||
|
var result = new JwtTokenManagerResult
|
||
|
{
|
||
|
Token = tokenResponse.AccessToken,
|
||
|
TokenType = tokenType,
|
||
|
ExpiresAt = expiresAt,
|
||
|
ExpiresIn = tokenResponse.ExpiresIn,
|
||
|
};
|
||
|
|
||
|
if (null != memoryCache && expiration < _cacheExpirationOffset)
|
||
|
{
|
||
|
logger?.LogWarning("Caching is enable but the token expiration time [{expiration}] is less than the expiration offset [{cacheExpirationOffset}]. Caching is ignored, please validate your authorization server configuration and the {className} cache expiration offset configuration.",
|
||
|
expiration, _cacheExpirationOffset, nameof(JwtTokenManagerService));
|
||
|
}
|
||
|
else
|
||
|
memoryCache?.Set(cacheOptions.CacheKey, result, expiration.Subtract(_cacheExpirationOffset));
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
}
|