Add authentication support with login/logout UI
- Introduced AuthService, IAuthApiClient, and AuthApiClient for managing authentication state and API calls (login, logout, session restore). - Added Login.razor and LoginLayout.razor for the login page, including styling and logic. - MainLayout.razor now checks authentication on load, restores sessions from sessionStorage, and redirects to /login if unauthenticated. Displays username and logout button when logged in. - Implemented JS interop (authStorage) for persisting authentication info in sessionStorage. - Registered AuthService, CookieContainer, and API clients in Program.cs to share cookies and support authentication. - Updated AppSettings and appsettings files to support separate ApiBaseUrl and DataApiBaseUrl. - Minor CSS improvements for username display in the top bar.
This commit is contained in:
64
DbFirst.BlazorWebApp/Services/AuthApiClient.cs
Normal file
64
DbFirst.BlazorWebApp/Services/AuthApiClient.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System.Net;
|
||||
|
||||
namespace DbFirst.BlazorWebApp.Services;
|
||||
|
||||
public class AuthApiClient(HttpClient httpClient, AuthService authService, CookieContainer cookieContainer) : IAuthApiClient
|
||||
{
|
||||
private const string LoginEndpoint = "api/Auth/db-first/login";
|
||||
private const string LogoutEndpoint = "api/Auth/logout";
|
||||
private const string CheckEndpoint = "api/Auth/check";
|
||||
|
||||
public async Task<bool> LoginAsync(string username, string password, CancellationToken ct = default)
|
||||
{
|
||||
var content = new MultipartFormDataContent();
|
||||
content.Add(new StringContent(username), "Username");
|
||||
content.Add(new StringContent(password), "Password");
|
||||
content.Add(new StringContent(string.Empty), "UserId");
|
||||
|
||||
var response = await httpClient.PostAsync(LoginEndpoint, content, ct);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return false;
|
||||
|
||||
var rawCookie = ExtractCookies();
|
||||
authService.SetAuthenticated(username, rawCookie);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task LogoutAsync(CancellationToken ct = default)
|
||||
{
|
||||
await httpClient.PostAsync(LogoutEndpoint, null, ct);
|
||||
authService.SetUnauthenticated();
|
||||
}
|
||||
|
||||
public async Task<bool> RestoreAsync(string username, string rawCookieHeader, CancellationToken ct = default)
|
||||
{
|
||||
RestoreCookies(rawCookieHeader);
|
||||
var response = await httpClient.GetAsync(CheckEndpoint, ct);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
authService.SetAuthenticated(username, rawCookieHeader);
|
||||
return true;
|
||||
}
|
||||
|
||||
authService.SetUnauthenticated();
|
||||
return false;
|
||||
}
|
||||
|
||||
private string ExtractCookies()
|
||||
{
|
||||
if (httpClient.BaseAddress is null) return string.Empty;
|
||||
var cookies = cookieContainer.GetCookies(httpClient.BaseAddress);
|
||||
return string.Join("; ", cookies.Cast<Cookie>().Select(c => $"{c.Name}={c.Value}"));
|
||||
}
|
||||
|
||||
private void RestoreCookies(string rawCookieHeader)
|
||||
{
|
||||
if (httpClient.BaseAddress is null) return;
|
||||
foreach (var part in rawCookieHeader.Split(';', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
var eqIdx = part.IndexOf('=');
|
||||
if (eqIdx > 0)
|
||||
cookieContainer.Add(httpClient.BaseAddress, new Cookie(part[..eqIdx].Trim(), part[(eqIdx + 1)..].Trim()));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user