2024-07-30 16:19:51 +02:00
|
|
|
|
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;
|
2024-10-27 22:09:46 +01:00
|
|
|
|
using WatchIt.Website.Services.Tokens;
|
|
|
|
|
|
using WatchIt.Website.Services.Client.Accounts;
|
2024-07-30 16:19:51 +02:00
|
|
|
|
|
2024-10-27 22:09:46 +01:00
|
|
|
|
namespace WatchIt.Website.Services.Authentication;
|
2024-07-30 16:19:51 +02:00
|
|
|
|
|
|
|
|
|
|
public class JWTAuthenticationStateProvider : AuthenticationStateProvider
|
|
|
|
|
|
{
|
|
|
|
|
|
#region SERVICES
|
|
|
|
|
|
|
|
|
|
|
|
private readonly HttpClient _httpClient;
|
|
|
|
|
|
|
|
|
|
|
|
private readonly ILogger<JWTAuthenticationStateProvider> _logger;
|
|
|
|
|
|
|
|
|
|
|
|
private readonly ITokensService _tokensService;
|
2024-10-27 22:09:46 +01:00
|
|
|
|
private readonly IAccountsClientService _accountsService;
|
2024-07-30 16:19:51 +02:00
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#region CONSTRUCTORS
|
|
|
|
|
|
|
2024-10-27 22:09:46 +01:00
|
|
|
|
public JWTAuthenticationStateProvider(HttpClient httpClient, ILogger<JWTAuthenticationStateProvider> logger, ITokensService tokensService, IAccountsClientService accountsService)
|
2024-07-30 16:19:51 +02:00
|
|
|
|
{
|
|
|
|
|
|
_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)
|
|
|
|
|
|
{
|
2024-09-11 15:59:13 +02:00
|
|
|
|
AuthenticateResponse? response = null;
|
2024-09-17 20:32:22 +02:00
|
|
|
|
|
|
|
|
|
|
void SetResponse(AuthenticateResponse data)
|
|
|
|
|
|
{
|
|
|
|
|
|
response = data;
|
|
|
|
|
|
}
|
2024-07-30 16:19:51 +02:00
|
|
|
|
|
2024-09-17 20:32:22 +02:00
|
|
|
|
await _accountsService.AuthenticateRefresh(SetResponse);
|
2024-09-11 15:59:13 +02:00
|
|
|
|
|
|
|
|
|
|
if (response is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
await _tokensService.SaveAuthenticationData(response);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
await _tokensService.RemoveAuthenticationData();
|
|
|
|
|
|
}
|
2024-07-30 16:19:51 +02:00
|
|
|
|
|
2024-09-11 15:59:13 +02:00
|
|
|
|
return response?.AccessToken;
|
2024-07-30 16:19:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
{
|
2024-09-17 20:32:22 +02:00
|
|
|
|
DateTime date = new DateTime(1970, 1, 1, 0, 0, 0, 0);
|
|
|
|
|
|
date = date.AddSeconds(timestamp);
|
|
|
|
|
|
return date;
|
2024-07-30 16:19:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
}
|