2024-12-20 01:50:06 -05:00
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
{
2024-12-21 02:07:13 -05:00
private readonly HttpClient _httpClient = httpClientFactory . CreateClient ( ) ;
2024-12-20 01:50:06 -05:00
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 ;
}
}
2024-12-21 02:07:13 -05:00
2024-12-21 02:05:30 -05:00
var formContent = new FormUrlEncodedContent ( [
2024-12-20 01:50:06 -05:00
new KeyValuePair < string , string > ( "grant_type" , "client_credentials" ) ,
new KeyValuePair < string , string > ( "client_id" , options . ClientId ) ,
new KeyValuePair < string , string > ( "client_secret" , options . ClientSecret ) ,
2024-12-21 02:05:30 -05:00
new KeyValuePair < string , string > ( "scopes" , string . Join ( " " , _scopes ) )
] ) ;
2024-12-20 01:50:06 -05:00
var request = new HttpRequestMessage ( HttpMethod . Post , options . TokenEndpoint )
{
Content = formContent
} ;
2024-12-21 02:05:30 -05:00
2024-12-20 01:50:06 -05:00
request . Headers . Accept . Add ( new MediaTypeWithQualityHeaderValue ( "application/json" ) ) ;
2024-12-21 02:07:13 -05:00
var response = await _httpClient . SendAsync ( request , cancellationToken ) ;
2024-12-20 01:50:06 -05:00
response . EnsureSuccessStatusCode ( ) ;
var tokenResponse = await response . Content . ReadFromJsonAsync < JwtTokenResponse > ( SnakeCaseOptions , cancellationToken ) ;
if ( tokenResponse = = null )
{
2024-12-21 02:05:30 -05:00
throw new InvalidOperationException ( "Failed to deserialize the token response content." ) ;
2024-12-20 01:50:06 -05:00
}
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
{
2024-12-20 02:29:20 -05:00
AccessToken = tokenResponse . AccessToken ,
2024-12-20 01:50:06 -05:00
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 ;
}
}