318 lines
11 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
} |