feat(client): update AuthService to use encrypted login

This commit is contained in:
Joseph Doherty
2026-01-03 08:39:18 -05:00
parent 30153dcbf8
commit 3468402200
5 changed files with 36 additions and 70 deletions
@@ -8,7 +8,6 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Blazor.SubtleCrypto" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.1" />
@@ -1,15 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace JdeScoping.Client.Models;
/// <summary>
/// Login form model with validation.
/// </summary>
public class LoginModel
{
[Required(ErrorMessage = "Username is required")]
public string Username { get; set; } = string.Empty;
[Required(ErrorMessage = "Password is required")]
public string Password { get; set; } = string.Empty;
}
@@ -1,4 +1,5 @@
@page "/login" @page "/login"
@using JdeScoping.Core.Models.Auth
@inject IAuthService AuthService @inject IAuthService AuthService
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@@ -1,74 +1,66 @@
using System.Net.Http.Json; using System.Net.Http.Json;
using JdeScoping.Client.Auth; using JdeScoping.Client.Auth;
using JdeScoping.Client.Models; using JdeScoping.Client.Models;
using JdeScoping.Core.Models.Auth;
namespace JdeScoping.Client.Services; namespace JdeScoping.Client.Services;
/// <summary> /// <summary>
/// Handles authentication via API calls with cookie-based auth. /// Handles authentication via encrypted API calls with cookie-based auth.
/// </summary> /// </summary>
public class AuthService : IAuthService public class AuthService : IAuthService
{ {
private readonly HttpClient _httpClient; private readonly HttpClient _httpClient;
private readonly ICryptoService _cryptoService;
private readonly AuthStateProvider _authStateProvider; private readonly AuthStateProvider _authStateProvider;
public AuthService( public AuthService(
HttpClient httpClient, HttpClient httpClient,
ICryptoService cryptoService,
AuthStateProvider authStateProvider) AuthStateProvider authStateProvider)
{ {
_httpClient = httpClient; _httpClient = httpClient;
_cryptoService = cryptoService;
_authStateProvider = authStateProvider; _authStateProvider = authStateProvider;
} }
public async Task<AuthResult> LoginAsync(LoginModel model) public async Task<LoginResultModel> LoginAsync(LoginModel model)
{ {
try try
{ {
var response = await _httpClient.PostAsJsonAsync("api/auth/login", new // Encrypt credentials
var encryptedData = await _cryptoService.EncryptLoginAsync(model);
var request = new EncryptedLoginRequest(encryptedData);
// Send encrypted request
var response = await _httpClient.PostAsJsonAsync("api/auth/login", request);
var result = await response.Content.ReadFromJsonAsync<LoginResultModel>();
if (result is null)
{ {
model.Username, return new LoginResultModel(false, "Invalid response from server", null);
model.Password
});
if (response.IsSuccessStatusCode)
{
// API returns UserInfo and sets auth cookie
var userInfo = await response.Content.ReadFromJsonAsync<UserInfoViewModel>();
if (userInfo != null)
{
// Notify auth state provider of the login
await _authStateProvider.MarkUserAsAuthenticated(userInfo);
return new AuthResult
{
Success = true,
User = userInfo
};
}
return new AuthResult
{
Success = false,
ErrorMessage = "Invalid response from server"
};
} }
var errorContent = await response.Content.ReadAsStringAsync(); if (result.Success && result.User is not null)
return new AuthResult
{ {
Success = false, // Notify auth state provider of the login
ErrorMessage = string.IsNullOrEmpty(errorContent) var userViewModel = new UserInfoViewModel
? "Login failed. Please check your credentials." {
: errorContent Username = result.User.Username,
}; FirstName = result.User.FirstName,
LastName = result.User.LastName,
DisplayName = result.User.DisplayName,
EmailAddress = result.User.EmailAddress,
Title = result.User.Title
};
await _authStateProvider.MarkUserAsAuthenticated(userViewModel);
}
return result;
} }
catch (Exception ex) catch (Exception ex)
{ {
return new AuthResult return new LoginResultModel(false, $"Login failed: {ex.Message}", null);
{
Success = false,
ErrorMessage = $"Login failed: {ex.Message}"
};
} }
} }
@@ -76,7 +68,6 @@ public class AuthService : IAuthService
{ {
try try
{ {
// Call logout endpoint to clear server-side cookie
await _httpClient.PostAsync("api/auth/logout", null); await _httpClient.PostAsync("api/auth/logout", null);
} }
catch catch
@@ -1,4 +1,4 @@
using JdeScoping.Client.Models; using JdeScoping.Core.Models.Auth;
namespace JdeScoping.Client.Services; namespace JdeScoping.Client.Services;
@@ -8,22 +8,12 @@ namespace JdeScoping.Client.Services;
public interface IAuthService public interface IAuthService
{ {
/// <summary> /// <summary>
/// Attempts to log in with the provided credentials. /// Attempts to log in with the provided credentials (encrypted).
/// </summary> /// </summary>
Task<AuthResult> LoginAsync(LoginModel model); Task<LoginResultModel> LoginAsync(LoginModel model);
/// <summary> /// <summary>
/// Logs out the current user. /// Logs out the current user.
/// </summary> /// </summary>
Task LogoutAsync(); Task LogoutAsync();
} }
/// <summary>
/// Result of an authentication attempt.
/// </summary>
public record AuthResult
{
public bool Success { get; init; }
public string? ErrorMessage { get; init; }
public UserInfoViewModel? User { get; init; }
}