Added authenication/authorization. Refactored api startup.
This commit is contained in:
29
JSMR.UI.Blazor/Services/AuthenticationClient.cs
Normal file
29
JSMR.UI.Blazor/Services/AuthenticationClient.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
|
||||
namespace JSMR.UI.Blazor.Services;
|
||||
|
||||
public class AuthenticationClient(HttpClient http)
|
||||
{
|
||||
public async Task<bool> LoginAsync(string username, string password, CancellationToken ct = default)
|
||||
{
|
||||
var resp = await http.PostAsJsonAsync("/auth/login", new { username, password }, ct);
|
||||
return resp.IsSuccessStatusCode;
|
||||
}
|
||||
|
||||
public async Task LogoutAsync(CancellationToken ct = default)
|
||||
=> await http.PostAsync("/auth/logout", content: null, ct);
|
||||
|
||||
public async Task<MeResponse?> GetMeAsync(CancellationToken ct = default)
|
||||
{
|
||||
var resp = await http.GetAsync("/api/me", ct);
|
||||
|
||||
if (resp.StatusCode == HttpStatusCode.Unauthorized)
|
||||
return null;
|
||||
|
||||
resp.EnsureSuccessStatusCode();
|
||||
return await resp.Content.ReadFromJsonAsync<MeResponse>(cancellationToken: ct);
|
||||
}
|
||||
|
||||
public sealed record MeResponse(string? name, string? id, string? role);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Http;
|
||||
|
||||
namespace JSMR.UI.Blazor.Services;
|
||||
|
||||
public sealed class IncludeRequestCredentialsMessageHandler : DelegatingHandler
|
||||
{
|
||||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
// Tells browser fetch() to send cookies/auth headers on cross-origin requests
|
||||
request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
|
||||
|
||||
return base.SendAsync(request, cancellationToken);
|
||||
}
|
||||
}
|
||||
21
JSMR.UI.Blazor/Services/JwtAuthorizationMessageHandler.cs
Normal file
21
JSMR.UI.Blazor/Services/JwtAuthorizationMessageHandler.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Net.Http.Headers;
|
||||
|
||||
namespace JSMR.UI.Blazor.Services;
|
||||
|
||||
public sealed class JwtAuthorizationMessageHandler(TokenStore tokens) : DelegatingHandler
|
||||
{
|
||||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken ct)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(tokens.AccessToken))
|
||||
{
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken);
|
||||
}
|
||||
|
||||
return base.SendAsync(request, ct);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TokenStore
|
||||
{
|
||||
public string? AccessToken { get; set; }
|
||||
}
|
||||
28
JSMR.UI.Blazor/Services/SessionState.cs
Normal file
28
JSMR.UI.Blazor/Services/SessionState.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
namespace JSMR.UI.Blazor.Services;
|
||||
|
||||
public sealed class SessionState(AuthenticationClient auth)
|
||||
{
|
||||
public AuthenticationClient.MeResponse? Me { get; private set; }
|
||||
public bool IsAuthenticated => Me is not null;
|
||||
|
||||
public event Action? Changed;
|
||||
|
||||
public async Task RefreshAsync(CancellationToken ct = default)
|
||||
{
|
||||
Me = await auth.GetMeAsync(ct);
|
||||
Changed?.Invoke();
|
||||
}
|
||||
|
||||
public async Task<bool> LoginAsync(string username, string password, CancellationToken ct = default)
|
||||
{
|
||||
var ok = await auth.LoginAsync(username, password, ct);
|
||||
await RefreshAsync(ct);
|
||||
return ok;
|
||||
}
|
||||
|
||||
public async Task LogoutAsync(CancellationToken ct = default)
|
||||
{
|
||||
await auth.LogoutAsync(ct);
|
||||
await RefreshAsync(ct);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user