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:
Ho Ngoc Hai
2026-03-04 10:27:56 +07:00
parent 710f87a94d
commit 4a1094b080
11 changed files with 41 additions and 5 deletions

View File

@@ -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.

View File

@@ -222,6 +222,7 @@
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
try
{
var shops = await DataService.GetShopsAsync();

View File

@@ -204,6 +204,7 @@
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
IsLoading = true;
try
{

View File

@@ -232,6 +232,7 @@
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
IsLoading = true;
try
{

View File

@@ -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()
{

View File

@@ -214,6 +214,7 @@ else
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
await LoadRoles();
}

View File

@@ -252,6 +252,7 @@ else
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
await LoadData();
}

View File

@@ -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()
{

View File

@@ -132,6 +132,7 @@ else
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
try { _logs = await IamService.GetAuditLogsAsync(); }
catch { _logs = new(); }
finally { _loading = false; }

View File

@@ -71,6 +71,7 @@
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
IsLoading = true;
try { _devices = await DataService.GetDevicesAsync(); }
catch { } finally { IsLoading = false; }

View File

@@ -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);
}