dotnet-cakemail-sdk/CM.Authentication/JwtTokenManagerService.cs

83 lines
3.2 KiB
C#

using System.Net.Http.Headers;
using System.Net.Http.Json;
using CM.Authentication.Abstractions;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace CM.Authentication;
public class JwtTokenManagerService(IOptions<AuthenticationOptions> options, IHttpClientFactory httpClientFactory, IMemoryCache? memoryCache, ILogger<JwtTokenManagerService>? logger)
: IJwtTokenManagerService
{
private readonly AuthenticationOptions _options = options.Value;
private readonly JwtTokenCacheOptions _cacheOptions = options.Value.CacheOptions;
private readonly HttpClient _httpClient = httpClientFactory.CreateClient();
private readonly TimeSpan _cacheExpirationOffset = TimeSpan.FromSeconds(options.Value.CacheOptions.ExpirationOffset);
public async Task<JwtTokenResult> GetTokenAsync(CancellationToken cancellationToken = default)
{
if (memoryCache != null)
{
var memoryGetValueResult = memoryCache.TryGetValue(_cacheOptions.CacheKey, out JwtTokenResult? cachedToken);
if (memoryGetValueResult && null != cachedToken)
{
return cachedToken;
}
}
var formContentKeyValues = new List<KeyValuePair<string, string>>()
{
new ("grant_type", "password"),
new ("username", _options.Username),
new ("password", _options.Password)
};
var formContent = new FormUrlEncodedContent(formContentKeyValues);
var request = new HttpRequestMessage(HttpMethod.Post, _options.TokenEndpoint)
{
Content = formContent
};
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await _httpClient.SendAsync(request, cancellationToken);
response.EnsureSuccessStatusCode();
var tokenResponse = await response.Content.ReadFromJsonAsync(JwtTokenManagerJsonContext.Default.JwtTokenResponse, cancellationToken);
if (tokenResponse is null)
throw new InvalidOperationException("Failed to deserialize the token response content.");
var parsedResult = Enum.TryParse<TokenType>(tokenResponse.TokenType, true, 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 JwtTokenResult
{
AccessToken = tokenResponse.AccessToken,
TokenType = tokenType,
Accounts = tokenResponse.Accounts,
RefreshToken = tokenResponse.RefreshToken,
ExpiresIn = tokenResponse.ExpiresIn,
ExpiresAt = expiresAt,
};
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;
}
}