wip: cookie-based auth with AllowAnonymous and absolute URIs
WBS-9.3 - NULL Policy CI Gate / NULL Policy Validation (push) Failing after 7s
Quant Engine CI/CD Pipeline / validate-core (push) Failing after 11s
Quant Engine CI/CD Pipeline / validate-ui-and-storage (push) Has been skipped
Deploy to Production / Build & Deploy to Production (push) Failing after 2m12s
WBS-9.3 - NULL Policy CI Gate / NULL Policy Validation (push) Failing after 7s
Quant Engine CI/CD Pipeline / validate-core (push) Failing after 11s
Quant Engine CI/CD Pipeline / validate-ui-and-storage (push) Has been skipped
Deploy to Production / Build & Deploy to Production (push) Failing after 2m12s
Changes: - Dashboard.razor: Add [AllowAnonymous] to allow page load before auth check - CustomAuthenticationStateProvider: Use absolute URIs for HttpClient calls - Fix JSON parsing: Use ReadAsStringAsync instead of ReadAsAsync - Implement cookie-first auth strategy with localStorage fallback Status: /dashboard still not loading after login Issues to investigate: - window.location.href redirect not working in Playwright - Set-Cookie headers not appearing in responses - JavaScript interop not available during static rendering Next: Direct browser testing vs Playwright environment issue Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
+63
-52
@@ -27,76 +27,87 @@ namespace QuantEngine.Web.Client.Infrastructure
|
||||
{
|
||||
try
|
||||
{
|
||||
string token = null;
|
||||
string username = null;
|
||||
string role = null;
|
||||
Console.WriteLine("[Auth] GetAuthenticationStateAsync called");
|
||||
|
||||
// Try to read from localStorage using JS interop (direct access)
|
||||
// Primary: Try to validate via /api/auth/me
|
||||
// This works with both cookies (automatic) and Bearer tokens
|
||||
try
|
||||
{
|
||||
token = await _jsRuntime.InvokeAsync<string>("localStorage.getItem", TokenKey);
|
||||
username = await _jsRuntime.InvokeAsync<string>("localStorage.getItem", UsernameKey);
|
||||
role = await _jsRuntime.InvokeAsync<string>("localStorage.getItem", RoleKey);
|
||||
Console.WriteLine("[Auth] Attempting validation via /api/auth/me (cookie or Bearer)...");
|
||||
var baseUrl = _http.BaseAddress?.AbsoluteUri ?? "http://localhost:5265";
|
||||
var meUrl = new Uri(new Uri(baseUrl), "api/auth/me").ToString();
|
||||
var meResponse = await _http.GetAsync(meUrl);
|
||||
|
||||
Console.WriteLine($"[Auth] JS interop: token={!string.IsNullOrWhiteSpace(token)}, username={username}");
|
||||
}
|
||||
catch (Exception jsEx)
|
||||
{
|
||||
Console.WriteLine($"[Auth] JS interop failed: {jsEx.Message}. Falling back to LocalStorageService...");
|
||||
|
||||
// Fallback to LocalStorageService
|
||||
token = await _localStorage.GetAsync<string>(TokenKey);
|
||||
username = await _localStorage.GetAsync<string>(UsernameKey);
|
||||
role = await _localStorage.GetAsync<string>(RoleKey);
|
||||
|
||||
Console.WriteLine($"[Auth] LocalStorageService: token={!string.IsNullOrWhiteSpace(token)}, username={username}");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(role))
|
||||
{
|
||||
role = "Admin";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(token) && !string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
try
|
||||
if (meResponse.IsSuccessStatusCode)
|
||||
{
|
||||
Console.WriteLine($"[Auth] Validating token with /api/auth/me...");
|
||||
var json = await meResponse.Content.ReadAsStringAsync();
|
||||
var meData = System.Text.Json.JsonDocument.Parse(json).RootElement;
|
||||
var authenticated = meData.TryGetProperty("authenticated", out var authProp) && authProp.GetBoolean();
|
||||
var username = meData.TryGetProperty("username", out var userProp) ? userProp.GetString() : null;
|
||||
var role = meData.TryGetProperty("role", out var roleProp) ? roleProp.GetString() : "Admin";
|
||||
|
||||
if (authenticated && !string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
Console.WriteLine($"[Auth] ✅ Authenticated via /api/auth/me: {username}");
|
||||
var identity = new ClaimsIdentity(new[]
|
||||
{
|
||||
new Claim(ClaimTypes.Name, username),
|
||||
new Claim(ClaimTypes.Role, role ?? "Admin")
|
||||
}, "QuantAdminAuth");
|
||||
|
||||
return new AuthenticationState(new ClaimsPrincipal(identity));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"[Auth] /api/auth/me returned {meResponse.StatusCode}");
|
||||
}
|
||||
}
|
||||
catch (Exception meEx)
|
||||
{
|
||||
Console.WriteLine($"[Auth] /api/auth/me failed: {meEx.Message}");
|
||||
}
|
||||
|
||||
// Fallback: Try to read from localStorage
|
||||
Console.WriteLine("[Auth] Fallback: checking localStorage...");
|
||||
try
|
||||
{
|
||||
string token = await _jsRuntime.InvokeAsync<string>("localStorage.getItem", TokenKey);
|
||||
string username = await _jsRuntime.InvokeAsync<string>("localStorage.getItem", UsernameKey);
|
||||
string role = await _jsRuntime.InvokeAsync<string>("localStorage.getItem", RoleKey);
|
||||
|
||||
Console.WriteLine($"[Auth] localStorage: token={!string.IsNullOrWhiteSpace(token)}, username={username}");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(token) && !string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
// Validate with server
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "api/auth/me");
|
||||
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||
var response = await _http.SendAsync(request);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
Console.WriteLine($"[Auth] /api/auth/me failed: {response.StatusCode}");
|
||||
await MarkUserAsLoggedOutAsync();
|
||||
return new AuthenticationState(_anonymous);
|
||||
Console.WriteLine($"[Auth] ✅ localStorage token validated: {username}");
|
||||
var identity = new ClaimsIdentity(new[]
|
||||
{
|
||||
new Claim(ClaimTypes.Name, username),
|
||||
new Claim(ClaimTypes.Role, role ?? "Admin")
|
||||
}, "QuantAdminAuth");
|
||||
|
||||
return new AuthenticationState(new ClaimsPrincipal(identity));
|
||||
}
|
||||
|
||||
Console.WriteLine($"[Auth] ✅ User authenticated: {username}");
|
||||
var identity = new ClaimsIdentity(new[]
|
||||
{
|
||||
new Claim(ClaimTypes.Name, username),
|
||||
new Claim(ClaimTypes.Role, role)
|
||||
}, "QuantAdminAuth");
|
||||
|
||||
var user = new ClaimsPrincipal(identity);
|
||||
return new AuthenticationState(user);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[Auth] Error during /api/auth/me call: {ex.Message}");
|
||||
// Fall through to anonymous
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception jsEx)
|
||||
{
|
||||
Console.WriteLine($"[Auth] ❌ No token or username found. token={!string.IsNullOrWhiteSpace(token)}, username={!string.IsNullOrWhiteSpace(username)}");
|
||||
Console.WriteLine($"[Auth] localStorage fallback failed: {jsEx.Message}");
|
||||
}
|
||||
|
||||
Console.WriteLine("[Auth] ❌ Not authenticated");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[Auth] Error accessing localStorage: {ex.Message}");
|
||||
Console.WriteLine($"[Auth] Unexpected error: {ex.Message}");
|
||||
}
|
||||
|
||||
return new AuthenticationState(_anonymous);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@page "/dashboard"
|
||||
@rendermode InteractiveWebAssembly
|
||||
@attribute [Microsoft.AspNetCore.Authorization.AllowAnonymous]
|
||||
@using QuantEngine.Core.Infrastructure
|
||||
@using Microsoft.AspNetCore.Components.Authorization
|
||||
@inject HttpClient Http
|
||||
|
||||
Reference in New Issue
Block a user