NonInventPurchasingSystem/CPRNIMS.Infrastructure/Helper/TokenHelper.cs
2026-01-26 14:21:31 +08:00

318 lines
11 KiB
C#

using CPRNIMS.Infrastructure.Dto.Account;
using CPRNIMS.Infrastructure.Models.Account;
using CPRNIMS.Infrastructure.ViewModel.Account;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Security.Claims;
using System.Text.Json;
namespace CPRNIMS.Infrastructure.Helper
{
public class TokenHelper
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly IConfiguration _configuration;
private readonly IHttpContextAccessor _httpContextAccessor;
public TokenHelper(
IHttpClientFactory httpClientFactory,
IConfiguration configuration,
IHttpContextAccessor httpContextAccessor)
{
_httpClientFactory = httpClientFactory;
_configuration = configuration;
_httpContextAccessor = httpContextAccessor;
}
public async Task<LoginResponse> LoginAsync(LoginVM loginModel)
{
var loginResponse = new LoginResponse();
try
{
var httpClient = _httpClientFactory.CreateClient("AuthApi");
var response = await httpClient.PostAsJsonAsync(
_configuration["Account:Login"],
loginModel);
loginResponse = JsonSerializer.Deserialize<LoginResponse>(
await response.Content.ReadAsStringAsync());
if (response.IsSuccessStatusCode && loginResponse != null)
{
return loginResponse;
}
else
{
var errorContent = await response.Content.ReadAsStringAsync();
loginResponse.message = errorContent;
return loginResponse;
}
}
catch (Exception ex)
{
loginResponse.message = ex.Message;
return loginResponse;
}
}
public async Task<string> GetValidTokenAsync()
{
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext?.User?.Identity?.IsAuthenticated != true)
return null;
// Get token from claims
var tokenClaim = httpContext.User.FindFirst("Token");
var expiryStr = httpContext.User.FindFirst("TokenExpiry")?.Value;
var refreshTokenClaim = httpContext.User.FindFirst("RefreshToken");
if (tokenClaim == null || string.IsNullOrEmpty(tokenClaim.Value))
return null;
// Check if token is expiring soon
if (!string.IsNullOrEmpty(expiryStr) &&
DateTime.TryParse(expiryStr, out DateTime expiry))
{
// If token expires in less than 5 minutes, refresh it
if (DateTime.UtcNow.AddMinutes(5) >= expiry)
{
if (refreshTokenClaim != null &&
!string.IsNullOrEmpty(refreshTokenClaim.Value))
{
var newTokenInfo = await RefreshTokenAsync(refreshTokenClaim.Value);
if (newTokenInfo != null)
{
// Update claims with new token
await UpdateTokenInClaims(newTokenInfo);
return newTokenInfo.AccessToken;
}
return null; // Refresh failed
}
}
}
return tokenClaim.Value;
}
private async Task UpdateTokenInClaims(TokenInfo tokenInfo)
{
var httpContext = _httpContextAccessor.HttpContext;
var currentPrincipal = httpContext.User;
// Create new claims list with updated token
var claims = currentPrincipal.Claims.Where(c =>
c.Type != "Token" &&
c.Type != "TokenExpiry" &&
c.Type != "RefreshToken").ToList();
claims.Add(new Claim("Token", tokenInfo.AccessToken));
claims.Add(new Claim("TokenExpiry", tokenInfo.ExpiresAt.ToString("O")));
if (!string.IsNullOrEmpty(tokenInfo.RefreshToken))
claims.Add(new Claim("RefreshToken", tokenInfo.RefreshToken));
var identity = new ClaimsIdentity(claims,
CookieAuthenticationDefaults.AuthenticationScheme);
await httpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(identity),
new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.AddHours(2),
AllowRefresh = true
});
}
private async Task<TokenInfo> RefreshTokenAsync(string refreshToken)
{
try
{
var httpClient = _httpClientFactory.CreateClient("AuthApi");
var response = await httpClient.PostAsJsonAsync(
_configuration["Account:Refresh"],
new { refreshToken });
if (response.IsSuccessStatusCode)
{
var loginResponse = JsonSerializer.Deserialize<LoginResponse>(
await response.Content.ReadAsStringAsync());
var expiresAt = CalculateExpiration(loginResponse);
return new TokenInfo
{
AccessToken = loginResponse.token,
RefreshToken = loginResponse.refreshToken,
ExpiresAt = expiresAt,
IssuedAt = DateTime.UtcNow,
Claims = ExtractClaimsFromToken(loginResponse.token)
};
}
}
catch (Exception)
{
// Refresh failed
}
return null;
}
private DateTime CalculateExpiration(LoginResponse response)
{
// Try multiple sources for expiration
if (response.expiresInSeconds > 0)
{
return DateTime.UtcNow.AddSeconds(response.expiresInSeconds);
}
else if (response.expiresAt > DateTime.MinValue && response.expiresAt.Year > 1)
{
return response.expiresAt;
}
else if (!string.IsNullOrEmpty(response.token))
{
var expiry = ExtractExpirationFromToken(response.token);
if (expiry > DateTime.MinValue)
return expiry;
}
// Default: 2 hours
return DateTime.UtcNow.AddHours(2);
}
private DateTime ExtractExpirationFromToken(string token)
{
try
{
var handler = new JwtSecurityTokenHandler();
var jwtToken = handler.ReadJwtToken(token);
if (jwtToken.ValidTo != DateTime.MinValue && jwtToken.ValidTo.Year > 1)
{
return jwtToken.ValidTo;
}
// Check exp claim
var expClaim = jwtToken.Claims.FirstOrDefault(c => c.Type == "exp");
if (expClaim != null && long.TryParse(expClaim.Value, out long exp))
{
return DateTimeOffset.FromUnixTimeSeconds(exp).UtcDateTime;
}
}
catch
{
// Token parsing failed
}
return DateTime.MinValue;
}
private Dictionary<string, string> ExtractClaimsFromToken(string token)
{
var claims = new Dictionary<string, string>();
try
{
var handler = new JwtSecurityTokenHandler();
var jwtToken = handler.ReadJwtToken(token);
foreach (var claim in jwtToken.Claims)
{
if (!claims.ContainsKey(claim.Type))
{
claims[claim.Type] = claim.Value;
}
}
}
catch (Exception)
{
// Token parsing failed
}
return claims;
}
public Dictionary<string, string> GetStoredClaims()
{
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext?.User?.Identity?.IsAuthenticated != true)
return new Dictionary<string, string>();
var tokenClaim = httpContext.User.FindFirst("Token");
if (tokenClaim == null || string.IsNullOrEmpty(tokenClaim.Value))
return new Dictionary<string, string>();
return ExtractClaimsFromToken(tokenClaim.Value);
}
// Rest of your existing methods...
public HttpClient CreateHttpClientWithDefaultHeaders(string token)
{
string BaseUrl = _configuration["CommonEndpoints:ApiDefaultHeaders:BaseUrl"];
var httpClient = new HttpClient(new HttpClientHandler
{
ServerCertificateCustomValidationCallback =
(sender, cert, chain, sslPolicyErrors) => true
})
{
BaseAddress = new Uri(BaseUrl)
};
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var customHeaders = CustomHeaders;
foreach (var header in customHeaders)
{
httpClient.DefaultRequestHeaders.Add(header.Key, header.Value);
}
return httpClient;
}
public Dictionary<string, string> DefaultHeaders
{
get
{
var headersSection = _configuration.GetSection(
"CommonEndpoints:ApiDefaultHeaders");
var headers = new Dictionary<string, string>();
foreach (var childSection in headersSection.GetChildren())
{
headers[childSection.Key] = childSection.Value;
}
return headers;
}
}
public Dictionary<string, string> CustomHeaders
{
get
{
var headersSection = _configuration.GetSection(
"CommonEndpoints:CustomApiHeaders");
var headers = new Dictionary<string, string>();
foreach (var childSection in headersSection.GetChildren())
{
headers[childSection.Key] = childSection.Value;
}
return headers;
}
}
}
}