using IdentityModel.Client; using PostFundManagement.Domain.Configuration.Security; namespace PostFundManagement.Application.Handlers; public class ClientCredentialsTokenHandler(ILogger logger, IOptions options) : DelegatingHandler { private readonly MachineIdentitySettings _settings = options.Value; private string? _cachedToken; private DateTime _tokenExpiry = DateTime.MinValue; protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { try { var token = await GetAccessTokenAsync(cancellationToken); if (string.IsNullOrWhiteSpace(token)) { logger.LogError("Failed to acquire access token for Skoruba API"); throw new InvalidOperationException("Unable to acquire access token"); } request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); logger.LogDebug("ClientCredentialsTokenHandler: Added bearer token to request"); } catch (Exception ex) { logger.LogError(ex, "Error acquiring client credentials token"); throw; } try { var response = await base.SendAsync(request, cancellationToken); if (!response.IsSuccessStatusCode) { logger.LogWarning("HTTP request failed with status {StatusCode} for {Method} {RequestUri}", response.StatusCode, request.Method, request.RequestUri); } return response; } catch (Exception ex) { logger.LogError(ex, "Error sending HTTP request with client credentials token"); throw; } } private async Task GetAccessTokenAsync(CancellationToken cancellationToken) { if (!string.IsNullOrWhiteSpace(_cachedToken) && DateTime.UtcNow < _tokenExpiry) { logger.LogDebug("Using cached access token"); return _cachedToken; } logger.LogDebug("Acquiring new access token from {Authority}", _settings.Authority); using var tokenClient = new HttpClient(); try { if (!_settings.Authority!.EndsWith('/')) _settings.Authority += "/"; var scope = _settings.Scope; var tokenRequest = new ClientCredentialsTokenRequest { Address = $"{_settings.Authority}connect/token", ClientId = _settings.ClientId!, ClientSecret = _settings.ClientSecret, GrantType = "client_credentials", Scope = scope, }; logger.LogDebug("Sending token request to {TokenEndpoint} for client {ClientId} with scope {Scope}", tokenRequest.Address, tokenRequest.ClientId, tokenRequest.Scope); var response = await tokenClient.RequestClientCredentialsTokenAsync(tokenRequest, cancellationToken); if (response.IsError) { logger.LogError("Token request error: {Error} - {ErrorDescription}. Full response: {Raw}", response.Error, response.ErrorDescription, response.Raw); return null; } _cachedToken = response.AccessToken; var expiresIn = response.ExpiresIn > 0 ? response.ExpiresIn : 3600; _tokenExpiry = DateTime.UtcNow.AddSeconds(expiresIn * 0.9); logger.LogDebug("Successfully acquired access token, expires at {Expiry}", _tokenExpiry); return _cachedToken; } catch (Exception ex) { logger.LogError(ex, "Exception occurred while acquiring client credentials token"); throw; } } }