fix: enable WASM-first auth flow for dashboard

Key changes:
- Add @rendermode InteractiveWebAssembly to Dashboard.razor
  (CLAUDE.md mandates Interactive WebAssembly as default)
- Reorder MapRazorComponents: WebAssembly first, then Server
- Simplify login.html: just redirect after 2s (no fetch verification)

Root cause of 302 redirect loop:
- Dashboard was rendering server-side (no @rendermode specified)
- Server-side rendering can't access localStorage
- CustomAuthenticationStateProvider read empty token
- Dashboard redirected to /login
- Result: 302 loop

Solution: Force client-side WASM rendering so:
1. Blazor WASM loads in browser
2. CustomAuthenticationStateProvider accesses localStorage
3. Token is read from localStorage
4. /api/auth/me validates token
5. User is authenticated
6. Dashboard displays

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 01:38:09 +09:00
parent 53ae2fcc51
commit eae0a68f06
9 changed files with 272 additions and 7 deletions
@@ -1,4 +1,5 @@
@page "/dashboard"
@rendermode InteractiveWebAssembly
@using QuantEngine.Core.Infrastructure
@using Microsoft.AspNetCore.Components.Authorization
@inject HttpClient Http
@@ -244,13 +245,18 @@
{
// Check authentication
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
Console.WriteLine($"[Dashboard] Auth state received. IsAuthenticated: {authState.User.Identity?.IsAuthenticated}");
if (!authState.User.Identity?.IsAuthenticated ?? true)
{
// Not authenticated - redirect to login
Console.WriteLine("[Dashboard] Not authenticated. Redirecting to login...");
NavManager.NavigateTo("/login.html", forceLoad: true);
return;
}
Console.WriteLine($"[Dashboard] Authenticated as: {authState.User.Identity?.Name}");
try
{
// Load operational report
+1 -1
View File
@@ -496,8 +496,8 @@ app.MapRazorPages();
// Map Blazor Components - catches all remaining routes
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()
.AddInteractiveServerRenderMode()
.AddAdditionalAssemblies(typeof(QuantEngine.Web.Client._Imports).Assembly);
app.Run();
+13 -6
View File
@@ -302,16 +302,18 @@
// 토큰 저장 (Blazor 인증 상태에 필요)
if (data.accessToken) {
localStorage.setItem('quant_admin_access_token', data.accessToken);
console.log('[Login] Access token saved to localStorage');
console.log('[Login] Access token saved to localStorage: ' + data.accessToken.substring(0, 16) + '...');
}
// 사용자 정보 저장
if (data.username) {
localStorage.setItem('quant_admin_username', data.username);
console.log('[Login] Username saved: ' + data.username);
}
if (data.role) {
localStorage.setItem('quant_admin_role', data.role);
console.log('[Login] Role saved: ' + data.role);
}
// 아이디 자동입력 설정 저장
@@ -324,15 +326,20 @@
// 로그인 성공 메시지 표시
alertDiv.className = 'alert alert-success show';
alertDiv.textContent = '로그인 성공! 대시보드로 이동합니다.';
alertDiv.textContent = '로그인 성공! 대시보드로 이동합니다...';
// Blazor 앱이 로드될 시간 제공
// (대시보드는 이제 [Authorize] 없이 로드되고, 내부에서 인증 체크)
console.log('[Login] Token saved. Redirecting in 3 seconds...');
// localStorage 확인
const savedToken = localStorage.getItem('quant_admin_access_token');
console.log('[Login] Verifying localStorage - Token exists: ' + (savedToken ? 'YES' : 'NO'));
// Blazor WASM 앱이 로드될 시간 제공
// Dashboard는 이제 @rendermode InteractiveWebAssembly로 설정되어
// CustomAuthenticationStateProvider가 localStorage에서 토큰을 읽을 수 있습니다
console.log('[Login] Redirecting to dashboard in 2 seconds...');
setTimeout(() => {
console.log('[Login] Navigating to dashboard');
window.location.href = '/dashboard';
}, 3000);
}, 2000);
} else {
const data = await response.json();
alertDiv.className = 'alert alert-error show';