Website services organized
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Net.Http.Headers;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using WatchIt.Website.Services.Client.Accounts;
|
||||
using WatchIt.Website.Services.Tokens;
|
||||
|
||||
namespace WatchIt.Website.Services.Authentication;
|
||||
|
||||
public class AuthenticationService : IAuthenticationService
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
private readonly AuthenticationStateProvider _authenticationStateProvider;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ITokensService _tokensService;
|
||||
private readonly IAccountsClientService _accountsClientService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public AuthenticationService(AuthenticationStateProvider authenticationStateProvider, HttpClient httpClient, ITokensService tokensService, IAccountsClientService accountsClientService)
|
||||
{
|
||||
_authenticationStateProvider = authenticationStateProvider;
|
||||
_httpClient = httpClient;
|
||||
_tokensService = tokensService;
|
||||
_accountsClientService = accountsClientService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public async Task<User?> GetUserAsync()
|
||||
{
|
||||
AuthenticationState state = await _authenticationStateProvider.GetAuthenticationStateAsync();
|
||||
|
||||
if (!GetAuthenticationStatusAsync(state))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new User
|
||||
{
|
||||
Id = int.Parse(state.User.FindFirst(x => x.Type == JwtRegisteredClaimNames.Sub)!.Value),
|
||||
Username = state.User.FindFirst(x => x.Type == JwtRegisteredClaimNames.UniqueName)!.Value,
|
||||
Email = state.User.FindFirst(x => x.Type == JwtRegisteredClaimNames.Email)!.Value,
|
||||
IsAdmin = bool.Parse(state.User.FindFirst(x => x.Type == "admin")!.Value),
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<bool> GetAuthenticationStatusAsync()
|
||||
{
|
||||
AuthenticationState state = await _authenticationStateProvider.GetAuthenticationStateAsync();
|
||||
return GetAuthenticationStatusAsync(state);
|
||||
}
|
||||
|
||||
public async Task LogoutAsync()
|
||||
{
|
||||
string? refreshToken = await _tokensService.GetRefreshToken();
|
||||
if (refreshToken is not null)
|
||||
{
|
||||
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("refresh", refreshToken.Replace("\"", ""));
|
||||
await _accountsClientService.Logout();
|
||||
_httpClient.DefaultRequestHeaders.Authorization = null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
private bool GetAuthenticationStatusAsync(AuthenticationState state)
|
||||
{
|
||||
return state.User.HasClaim(x => x.Type == JwtRegisteredClaimNames.Iss && x.Value == "WatchIt");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace WatchIt.Website.Services.Authentication;
|
||||
|
||||
public interface IAuthenticationService
|
||||
{
|
||||
Task<User?> GetUserAsync();
|
||||
Task<bool> GetAuthenticationStatusAsync();
|
||||
Task LogoutAsync();
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
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.Tokens;
|
||||
using WatchIt.Website.Services.Client.Accounts;
|
||||
|
||||
namespace WatchIt.Website.Services.Authentication;
|
||||
|
||||
public class JWTAuthenticationStateProvider : AuthenticationStateProvider
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
private readonly ILogger<JWTAuthenticationStateProvider> _logger;
|
||||
|
||||
private readonly ITokensService _tokensService;
|
||||
private readonly IAccountsClientService _accountsService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public JWTAuthenticationStateProvider(HttpClient httpClient, ILogger<JWTAuthenticationStateProvider> logger, ITokensService tokensService, IAccountsClientService 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
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace WatchIt.Website.Services.Authentication;
|
||||
|
||||
public class User
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
public required long Id { get; init; }
|
||||
public required string Username { get; init; }
|
||||
public required string Email { get; init; }
|
||||
public required bool IsAdmin { get; init; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Components.Authorization">
|
||||
<HintPath>..\..\..\..\..\..\..\Program Files\dotnet\shared\Microsoft.AspNetCore.App\8.0.10\Microsoft.AspNetCore.Components.Authorization.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WatchIt.Website.Services.Client\WatchIt.Website.Services.Client.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.Website.Services.Tokens\WatchIt.Website.Services.Tokens.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.5.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user