Files
2024-09-17 20:32:22 +02:00

165 lines
4.5 KiB
C#

using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text.Json;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.Logging;
using WatchIt.Common.Model.Accounts;
using WatchIt.Website.Services.Utility.Tokens;
using WatchIt.Website.Services.WebAPI.Accounts;
namespace WatchIt.Website.Services.Utility.Authentication;
public class JWTAuthenticationStateProvider : AuthenticationStateProvider
{
#region SERVICES
private readonly HttpClient _httpClient;
private readonly ILogger<JWTAuthenticationStateProvider> _logger;
private readonly ITokensService _tokensService;
private readonly IAccountsWebAPIService _accountsService;
#endregion
#region CONSTRUCTORS
public JWTAuthenticationStateProvider(HttpClient httpClient, ILogger<JWTAuthenticationStateProvider> logger, ITokensService tokensService, IAccountsWebAPIService accountsService)
{
_httpClient = httpClient;
_logger = logger;
_tokensService = tokensService;
_accountsService = accountsService;
}
#endregion
#region PUBLIC METHODS
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
AuthenticationState state = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
Task<string?> accessTokenTask = _tokensService.GetAccessToken();
Task<string?> refreshTokenTask = _tokensService.GetRefreshToken();
await Task.WhenAll(accessTokenTask, refreshTokenTask);
string? accessToken = await accessTokenTask;
string? refreshToken = await refreshTokenTask;
bool refreshed = false;
if (string.IsNullOrWhiteSpace(accessToken))
{
if (string.IsNullOrWhiteSpace(refreshToken))
{
return state;
}
string? accessTokenNew = await Refresh(refreshToken);
if (string.IsNullOrWhiteSpace(accessToken))
{
return state;
}
accessToken = accessTokenNew;
refreshed = true;
}
IEnumerable<Claim> claims = GetClaimsFromToken(accessToken);
Claim? expClaim = claims.FirstOrDefault(c => c.Type == "exp");
if (expClaim is not null && ConvertFromUnixTimestamp(int.Parse(expClaim.Value)) > DateTime.UtcNow)
{
if (refreshed)
{
return state;
}
}
else
{
if (string.IsNullOrWhiteSpace(refreshToken))
{
return state;
}
string? accessTokenNew = await Refresh(refreshToken);
if (accessTokenNew is null)
{
return state;
}
accessToken = accessTokenNew;
}
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Replace("\"", ""));
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(claims)));
}
#endregion
#region PRIVATE METHODS
private async Task<string?> Refresh(string refreshToken)
{
AuthenticateResponse? response = null;
void SetResponse(AuthenticateResponse data)
{
response = data;
}
await _accountsService.AuthenticateRefresh(SetResponse);
if (response is not null)
{
await _tokensService.SaveAuthenticationData(response);
}
else
{
await _tokensService.RemoveAuthenticationData();
}
return response?.AccessToken;
}
private static IEnumerable<Claim> GetClaimsFromToken(string token)
{
string payload = token.Split('.')[1];
switch (payload.Length % 4)
{
case 2: payload += "=="; break;
case 3: payload += "="; break;
}
byte[] jsonBytes = Convert.FromBase64String(payload);
Dictionary<string, object>? keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);
if (keyValuePairs is null)
{
throw new Exception("Incorrect token");
}
return keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString()));
}
public static DateTime ConvertFromUnixTimestamp(int timestamp)
{
DateTime date = new DateTime(1970, 1, 1, 0, 0, 0, 0);
date = date.AddSeconds(timestamp);
return date;
}
#endregion
}