Files
gridpilot.gg/apps/website/AUTH_FIXES_SUMMARY.md
2026-01-01 16:32:06 +01:00

203 lines
6.6 KiB
Markdown

# Authentication Loading State Fixes
## Problem
Users were experiencing an infinite "loading" state when accessing protected routes like `/dashboard`. The page would show "Loading..." indefinitely instead of either displaying the content or redirecting to login.
## Root Cause Analysis
The issue was caused by a mismatch between multiple authentication state management systems:
1. **AuthContext**: Managed session state and loading flag
2. **AuthorizationBlocker**: Determined access reasons based on session state
3. **AuthGateway**: Combined context and blocker state
4. **RouteGuard**: Handled UI rendering and redirects
### The Problem Flow:
```
1. User visits /dashboard
2. AuthContext initializes: session = null, loading = false
3. AuthGuard checks access
4. AuthorizationBlocker sees session = null → returns 'loading'
5. AuthGateway sees blocker.reason = 'loading' → sets isLoading = true
6. RouteGuard shows loading state
7. Session fetch completes: session = null, loading = false
8. But blocker still returns 'loading' because session is null
9. Infinite loading state
```
## Fixes Applied
### 1. AuthContext.tsx
**Problem**: Initial loading state was `false`, but session fetch wasn't tracked
**Fix**:
```typescript
// Before
const [loading, setLoading] = useState(false);
const fetchSession = useCallback(async () => {
try {
const current = await sessionService.getSession();
setSession(current);
} catch {
setSession(null);
}
}, [sessionService]);
// After
const [loading, setLoading] = useState(true); // Start with loading = true
const fetchSession = useCallback(async () => {
setLoading(true); // Set loading when starting fetch
try {
const current = await sessionService.getSession();
setSession(current);
} catch {
setSession(null);
} finally {
setLoading(false); // Clear loading when done
}
}, [sessionService]);
```
### 2. AuthGateway.ts
**Problem**: Was checking both `authContext.loading` AND `blocker.reason === 'loading'`
**Fix**: Only check authContext.loading for the isLoading state
```typescript
// Before
isLoading: this.authContext.loading || reason === 'loading',
// After
isLoading: this.authContext.loading,
```
### 3. AuthorizationBlocker.ts
**Problem**: Returned 'loading' when session was null, creating confusion
**Fix**: Treat null session as unauthenticated, not loading
```typescript
// Before
getReason(): AuthorizationBlockReason {
if (!this.currentSession) {
return 'loading';
}
// ...
}
// After
getReason(): AuthorizationBlockReason {
if (!this.currentSession) {
return 'unauthenticated'; // Null = unauthenticated
}
// ...
}
canExecute(): boolean {
const reason = this.getReason();
return reason === 'enabled'; // Only enabled grants access
}
```
### 4. RouteGuard.tsx
**Problem**: Generic loading message, unclear redirect flow
**Fix**: Better user feedback during authentication flow
```typescript
// Loading state shows verification message
if (accessState.isLoading) {
return loadingComponent || (
<div className="flex items-center justify-center min-h-screen">
<LoadingState message="Verifying authentication..." className="min-h-screen" />
</div>
);
}
// Unauthorized shows redirect message before redirecting
if (!accessState.canAccess && config.redirectOnUnauthorized !== false) {
return (
<div className="flex items-center justify-center min-h-screen">
<LoadingState message="Redirecting to login..." className="min-h-screen" />
</div>
);
}
```
### 5. Dashboard Page
**Problem**: Had redundant auth checks that conflicted with layout protection
**Fix**: Simplified to only handle data loading
```typescript
// Before: Had auth checks, useEffect for redirects, etc.
export default function DashboardPage() {
const router = useRouter();
const { session, loading: authLoading } = useAuth();
// ... complex auth logic
// After: Only handles data loading
export default function DashboardPage() {
const { data: dashboardData, isLoading, error } = useDashboardOverview();
// ... simple data loading
}
```
## New Authentication Flow
### Unauthenticated User:
1. User visits `/dashboard`
2. Middleware checks for `gp_session` cookie → not found
3. Middleware redirects to `/auth/login?returnTo=/dashboard`
4. User logs in
5. Session created, cookie set
6. Redirected back to `/dashboard`
7. AuthGuard verifies session exists
8. Dashboard loads
### Authenticated User:
1. User visits `/dashboard`
2. Middleware checks for `gp_session` cookie → found
3. Request proceeds to page rendering
4. AuthGuard shows "Verifying authentication..." (briefly)
5. Session verified via AuthContext
6. AuthGuard shows "Redirecting to login..." (if unauthorized)
7. Or renders dashboard content
### Loading State Resolution:
```
Initial: session=null, loading=true → AuthGuard shows "Verifying..."
Fetch completes: session=null, loading=false → AuthGuard redirects to login
```
## Files Modified
1. `apps/website/lib/auth/AuthContext.tsx` - Fixed loading state management
2. `apps/website/lib/gateways/AuthGateway.ts` - Simplified isLoading logic
3. `apps/website/lib/blockers/AuthorizationBlocker.ts` - Removed 'loading' reason
4. `apps/website/lib/gateways/RouteGuard.tsx` - Improved user feedback
5. `apps/website/app/dashboard/page.tsx` - Removed redundant auth checks
6. `apps/website/app/dashboard/layout.tsx` - Added AuthGuard protection
7. `apps/website/app/profile/layout.tsx` - Added AuthGuard protection
8. `apps/website/app/sponsor/layout.tsx` - Added AuthGuard protection
9. `apps/website/app/onboarding/layout.tsx` - Added AuthGuard protection
10. `apps/website/app/admin/layout.tsx` - Added RouteGuard protection
## Testing the Fix
### Expected Behavior:
- **Unauthenticated access**: Redirects to login within 500ms
- **Authenticated access**: Shows dashboard after brief verification
- **No infinite loading**: Loading states resolve properly
### Test Scenarios:
1. Clear cookies, visit `/dashboard` → Should redirect to login
2. Login, visit `/dashboard` → Should show dashboard
3. Login, clear cookies, refresh → Should redirect to login
4. Login as non-admin, visit `/admin` → Should redirect to login
## Security Notes
- **Defense in depth**: Multiple protection layers (middleware + layout + page)
- **No security bypass**: All fixes maintain security requirements
- **User experience**: Clear feedback during authentication flow
- **Performance**: Minimal overhead, only necessary checks
## Future Improvements
1. Add role-based access to SessionViewModel
2. Implement proper backend role system
3. Add session refresh mechanism
4. Implement proper token validation
5. Add authentication state persistence