feat(client): update AuthService to use encrypted login
This commit is contained in:
@@ -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; }
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user