fix(web-client-tpos): restore JWT from localStorage before admin API calls
Root cause: AdminBase had no auth initialization. AuthStateService is in-memory Singleton — token is null after page refresh. Admin pages called PosDataService without token → BFF forwarded requests without Authorization header → microservices returned 401. Fix: - AdminBase.cs: inject AuthService, call TryRestoreSessionAsync() - 9 admin pages: add await base.OnInitializedAsync() calls - BffHttpClient.cs: add debug logging for auth forwarding
This commit is contained in:
@@ -10,6 +10,18 @@ namespace WebClientTpos.Client.Pages.Admin;
|
||||
public abstract class AdminBase : ComponentBase
|
||||
{
|
||||
[Inject] protected NavigationManager NavigationManager { get; set; } = default!;
|
||||
[Inject] protected Services.AuthService AuthService { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Restore auth session from localStorage so API calls have JWT token.
|
||||
/// Subclasses that override OnInitializedAsync MUST call base.OnInitializedAsync().
|
||||
/// VI: Khôi phục session auth từ localStorage để API calls có JWT token.
|
||||
/// Các lớp con override OnInitializedAsync PHẢI gọi base.OnInitializedAsync().
|
||||
/// </summary>
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await AuthService.TryRestoreSessionAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Page title shown in topbar.
|
||||
|
||||
@@ -222,6 +222,7 @@
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
try
|
||||
{
|
||||
var shops = await DataService.GetShopsAsync();
|
||||
|
||||
@@ -204,6 +204,7 @@
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
IsLoading = true;
|
||||
try
|
||||
{
|
||||
|
||||
@@ -232,6 +232,7 @@
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
IsLoading = true;
|
||||
try
|
||||
{
|
||||
|
||||
@@ -2055,7 +2055,11 @@
|
||||
private string? _recipeFormMessage;
|
||||
private bool _recipeFormSuccess;
|
||||
|
||||
protected override async Task OnInitializedAsync() => await LoadData();
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
await LoadData();
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
|
||||
@@ -214,6 +214,7 @@ else
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
await LoadRoles();
|
||||
}
|
||||
|
||||
|
||||
@@ -252,6 +252,7 @@ else
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
await LoadData();
|
||||
}
|
||||
|
||||
|
||||
@@ -194,7 +194,11 @@
|
||||
|| (s.Slug?.Contains(SearchQuery, StringComparison.OrdinalIgnoreCase) ?? false)
|
||||
|| (s.Category?.Contains(SearchQuery, StringComparison.OrdinalIgnoreCase) ?? false));
|
||||
|
||||
protected override async Task OnInitializedAsync() => await LoadData();
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
await LoadData();
|
||||
}
|
||||
|
||||
private async Task LoadData()
|
||||
{
|
||||
|
||||
@@ -132,6 +132,7 @@ else
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
try { _logs = await IamService.GetAuditLogsAsync(); }
|
||||
catch { _logs = new(); }
|
||||
finally { _loading = false; }
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
IsLoading = true;
|
||||
try { _devices = await DataService.GetDevicesAsync(); }
|
||||
catch { } finally { IsLoading = false; }
|
||||
|
||||
@@ -11,10 +11,12 @@ namespace WebClientTpos.Server.Infrastructure;
|
||||
public class AuthForwardingHandler : DelegatingHandler
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly ILogger<AuthForwardingHandler> _logger;
|
||||
|
||||
public AuthForwardingHandler(IHttpContextAccessor httpContextAccessor)
|
||||
public AuthForwardingHandler(IHttpContextAccessor httpContextAccessor, ILogger<AuthForwardingHandler> logger)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override Task<HttpResponseMessage> SendAsync(
|
||||
@@ -23,8 +25,15 @@ public class AuthForwardingHandler : DelegatingHandler
|
||||
var incomingRequest = _httpContextAccessor.HttpContext?.Request;
|
||||
if (incomingRequest?.Headers.ContainsKey("Authorization") == true)
|
||||
{
|
||||
request.Headers.TryAddWithoutValidation(
|
||||
"Authorization", incomingRequest.Headers["Authorization"].ToString());
|
||||
var authHeader = incomingRequest.Headers["Authorization"].ToString();
|
||||
request.Headers.TryAddWithoutValidation("Authorization", authHeader);
|
||||
_logger.LogDebug("EN: Auth header forwarded ({Length} chars) to {Uri}",
|
||||
authHeader.Length, request.RequestUri);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("EN: No Authorization header found in incoming request for {Uri}. HttpContext null: {IsNull}",
|
||||
request.RequestUri, _httpContextAccessor.HttpContext == null);
|
||||
}
|
||||
return base.SendAsync(request, cancellationToken);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user