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

6.6 KiB

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:

// 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

// 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

// 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

// 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

// 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