using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Security.Principal; using System.Text; using System.Text.Json; using MesaFabApproval.Shared.Models; using Microsoft.Extensions.Caching.Memory; namespace MesaFabApproval.Client.Services; public interface IAuthenticationService { Task SendAuthenticationRequest(string loginId, string password); Task AttemptLocalUserAuth(); Task FetchAuthState(); Task ClearTokens(); Task ClearCurrentUser(); Task SetTokens(string jwt, string refreshToken); Task SetLoginId(string loginId); Task SetCurrentUser(User user); Task GetCurrentUser(); Task GetAuthTokens(); Task GetLoginId(); ClaimsPrincipal GetClaimsPrincipalFromJwt(string jwt); } public class AuthenticationService : IAuthenticationService { private readonly ILocalStorageService _localStorageService; private readonly IMemoryCache _cache; private readonly IHttpClientFactory _httpClientFactory; public AuthenticationService(ILocalStorageService localStorageService, IMemoryCache cache, IHttpClientFactory httpClientFactory) { _localStorageService = localStorageService ?? throw new ArgumentNullException("ILocalStorageService not injected"); _cache = cache ?? throw new ArgumentNullException("IMemoryCache not injected"); _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException("IHttpClientFactory not injected"); } public async Task SendAuthenticationRequest(string loginId, string password) { if (string.IsNullOrWhiteSpace(loginId)) throw new ArgumentException("loginId cannot be null or empty"); if (string.IsNullOrWhiteSpace(password)) throw new ArgumentException("password cannot be null or empty"); HttpClient httpClient = _httpClientFactory.CreateClient("API"); AuthAttempt authAttempt = new() { LoginID = loginId, Password = password }; HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "auth/login"); request.Content = new StringContent(JsonSerializer.Serialize(authAttempt), Encoding.UTF8, "application/json"); HttpResponseMessage httpResponseMessage = await httpClient.SendAsync(request); if (httpResponseMessage.IsSuccessStatusCode) { string responseContent = await httpResponseMessage.Content.ReadAsStringAsync(); JsonSerializerOptions jsonSerializerOptions = new() { PropertyNameCaseInsensitive = true }; LoginResult loginResult = JsonSerializer.Deserialize(responseContent, jsonSerializerOptions) ?? throw new Exception("Unable to parse login result from API response"); if (!loginResult.IsAuthenticated) throw new Exception($"User with Login ID {loginId} not authorized"); await SetLoginId(loginId); await SetTokens(loginResult.AuthTokens.JwtToken, loginResult.AuthTokens.RefreshToken); await SetCurrentUser(loginResult.User); ClaimsPrincipal principal = GetClaimsPrincipalFromJwt(loginResult.AuthTokens.JwtToken); return principal; } else { throw new Exception($"Login API request failed for {loginId}, because {httpResponseMessage.ReasonPhrase}"); } } public async Task AttemptLocalUserAuth() { WindowsIdentity? user = WindowsIdentity.GetCurrent(); if (user is null) throw new Exception("no authenticated user found"); if (!user.IsAuthenticated) throw new Exception("you are not authenticated"); HttpClient httpClient = _httpClientFactory.CreateClient("API"); HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "auth/login/localWindows"); request.Content = new StringContent(JsonSerializer.Serialize(user), Encoding.UTF8, "application/json"); HttpResponseMessage httpResponseMessage = await httpClient.SendAsync(request); if (httpResponseMessage.IsSuccessStatusCode) { string responseContent = await httpResponseMessage.Content.ReadAsStringAsync(); JsonSerializerOptions jsonSerializerOptions = new() { PropertyNameCaseInsensitive = true }; LoginResult loginResult = JsonSerializer.Deserialize(responseContent, jsonSerializerOptions) ?? throw new Exception("Unable to parse login result from API response"); if (!loginResult.IsAuthenticated) throw new Exception($"User with Login ID {user.Name} not authorized"); await SetLoginId(loginResult.User.LoginID); await SetTokens(loginResult.AuthTokens.JwtToken, loginResult.AuthTokens.RefreshToken); await SetCurrentUser(loginResult.User); ClaimsPrincipal principal = GetClaimsPrincipalFromJwt(loginResult.AuthTokens.JwtToken); return principal; } else { throw new Exception($"Login API request failed for {user.Name}, because {httpResponseMessage.ReasonPhrase}"); } } public async Task FetchAuthState() { string? jwt = _cache.Get("MesaFabApprovalJwt"); if (jwt is null) jwt = await _localStorageService.GetItem("MesaFabApprovalJwt"); if (jwt is null) throw new Exception("Unable to find JWT"); ClaimsPrincipal principal = GetClaimsPrincipalFromJwt(jwt); return principal; } public async Task ClearTokens() { _cache.Remove("MesaFabApprovalJwt"); await _localStorageService.RemoveItem("MesaFabApprovalJwt"); _cache.Remove("MesaFabApprovalRefreshToken"); await _localStorageService.RemoveItem("MesaFabApprovalRefreshToken"); } public async Task SetTokens(string jwt, string refreshToken) { if (string.IsNullOrWhiteSpace(jwt)) throw new ArgumentNullException("JWT cannot be null or empty"); if (string.IsNullOrWhiteSpace(refreshToken)) throw new ArgumentNullException("Refresh token cannot be null or empty"); _cache.Set("MesaFabApprovalJwt", jwt); await _localStorageService.AddItem("MesaFabApprovalJwt", jwt); _cache.Set("MesaFabApprovalRefreshToken", refreshToken); await _localStorageService.AddItem("MesaFabApprovalRefreshToken", refreshToken); } public async Task SetLoginId(string loginId) { if (string.IsNullOrWhiteSpace(loginId)) throw new ArgumentNullException("LoginId cannot be null or empty"); _cache.Set("MesaFabApprovalUserId", loginId); await _localStorageService.AddItem("MesaFabApprovalUserId", loginId); } public async Task SetCurrentUser(User user) { if (user is null) throw new ArgumentNullException("User cannot be null"); _cache.Set("MesaFabApprovalCurrentUser", user); await _localStorageService.AddItem("MesaFabApprovalCurrentUser", user); } public async Task ClearCurrentUser() { _cache.Remove("MesaFabApprovalCurrentUser"); await _localStorageService.RemoveItem("MesaFabApprovalCurrentUser"); _cache.Remove("MesaFabApprovalUserId"); await _localStorageService.RemoveItem("MesaFabApprovalUserId"); } public async Task GetCurrentUser() { User? currentUser = null; currentUser = _cache.Get("MesaFabApprovalCurrentUser") ?? await _localStorageService.GetItem("MesaFabApprovalCurrentUser"); return currentUser; } public async Task GetAuthTokens() { AuthTokens? authTokens = null; string? jwt = _cache.Get("MesaFabApprovalJwt"); if (jwt is null) jwt = await _localStorageService.GetItem("MesaFabApprovalJwt"); string? refreshToken = _cache.Get("MesaFabApprovalRefreshToken"); if (refreshToken is null) refreshToken = await _localStorageService.GetItem("MesaFabApprovalRefreshToken"); if (!string.IsNullOrWhiteSpace(jwt) && !string.IsNullOrWhiteSpace(refreshToken)) { authTokens = new() { JwtToken = jwt, RefreshToken = refreshToken }; } return authTokens; } public async Task GetLoginId() { string? loginId = _cache.Get("MesaFabApprovalUserId"); if (loginId is null) loginId = await _localStorageService.GetItem("MesaFabApprovalUserId"); return loginId; } public ClaimsPrincipal GetClaimsPrincipalFromJwt(string jwt) { if (string.IsNullOrWhiteSpace(jwt)) throw new ArgumentException("JWT cannot be null or empty"); JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler(); if (!tokenHandler.CanReadToken(jwt)) { throw new Exception("Unable to parse JWT from API"); } JwtSecurityToken jwtSecurityToken = tokenHandler.ReadJwtToken(jwt); ClaimsIdentity identity = new ClaimsIdentity(jwtSecurityToken.Claims, "MesaFabApprovalWasm"); return new(identity); } }