kjh2064
|
08e9e07458
|
fix: Critical runtime bug - TokenRefreshHandler JS interop in Blazor Server
TaxBaik CI/CD / build-and-deploy (push) Successful in 47s
**Problem:**
TokenRefreshHandler (DelegatingHandler) runs on a non-circuit thread.
ILocalStorageService (JS interop) only works during component render.
Production: 401 response → token refresh → JS interop fails silently.
**Solution:**
1. ITokenStore - Scoped in-memory token store (no JS interop)
- Properties: AccessToken, RefreshToken, TokenExpiryTicks
- Method: IsAccessTokenExpired()
2. TokenStore implementation
- Replaces localStorage as primary token source
- DelegatingHandler reads/writes only to TokenStore
- Pages reload → GetAuthenticationStateAsync restores from localStorage
3. CustomAuthenticationStateProvider
- Accepts ITokenStore injection
- LoginAsync: Write to both TokenStore + localStorage
- LogoutAsync: Clear both
- GetAuthenticationStateAsync: Read from TokenStore first, fallback to localStorage
4. AdminDashboardClient BaseAddress fix
- Was: new Uri("/taxbaik/api/") - relative URI (runtime error)
- Now: Configured in Program.cs as absolute URI
- Program.cs: AddHttpClient(..., client => client.BaseAddress = new Uri("http://localhost:5001/taxbaik/api/"))
**Architecture:**
- TokenStore: Scoped in-memory (DelegatingHandler use)
- localStorage: Persistent (page reload recovery)
- Pattern: Server-side token management without JS interop
This fixes the cascading failure that would occur on any 401 in production.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
2026-06-28 10:54:11 +09:00 |
|
kjh2064
|
58edbd9c8f
|
refactor: Phase 5 - JWT token lifecycle (Access + Refresh + Auto-refresh)
TaxBaik CI/CD / build-and-deploy (push) Successful in 48s
**Implementation:**
- AuthService: Split token generation
* AccessToken: 15 minutes
* RefreshToken: 7 days (10080 minutes)
* New: GenerateTokenPair() method
* New: RefreshAccessTokenAsync() method
- AuthTokenPair: New record (accessToken, refreshToken, expiresIn)
- AuthController: New /api/auth/refresh endpoint
* POST /api/auth/refresh?refreshToken=...
* Response: { accessToken, refreshToken, expiresIn }
* RefreshTokenRequest DTO
- TokenRefreshHandler: New DelegatingHandler
* Automatic Bearer token injection
* 401 response handling
* Auto-refresh with retry
* localStorage sync (accessToken, refreshToken, tokenExpiry)
- CustomAuthenticationStateProvider: Token storage split
* Before: auth_token (single)
* After: accessToken, refreshToken, tokenExpiry
* LoginAsync signature updated
- Login.razor: Handle token pair
* LoginResponse: { accessToken, refreshToken, expiresIn }
* Call new LoginAsync(accessToken, refreshToken, expiresIn)
- Program.cs: TokenRefreshHandler registration
* AddScoped<TokenRefreshHandler>()
* AdminDashboardClient pipeline: .AddHttpMessageHandler<TokenRefreshHandler>()
**SOLID Principles:**
✓ S (Single Responsibility): TokenRefreshHandler handles only token refresh
✓ D (Dependency Inversion): DelegatingHandler abstracts HTTP concerns
✓ O (Open/Closed): Token lifetime extension without code changes
**Security Pattern:**
- Short-lived access tokens (15min) reduce theft window
- Refresh tokens (7d) enable persistence without storing secrets
- Automatic refresh is transparent to components
**Flow:**
Blazor → AdminDashboardClient → TokenRefreshHandler (auto-add Bearer)
→ 401 → RefreshTokenAsync() → POST /api/auth/refresh
→ Store new pair → Retry original request
Status: Token lifecycle complete, ready for SignalR integration (Phase 6)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
2026-06-28 10:51:24 +09:00 |
|