357 lines
10 KiB
Markdown
357 lines
10 KiB
Markdown
# Authentication Protection Strategy
|
|
|
|
## Overview
|
|
|
|
GridPilot website implements a **defense-in-depth** authentication protection strategy using both **middleware-level** and **component-level** protection to ensure authenticated routes are properly secured.
|
|
|
|
## Protection Layers
|
|
|
|
### 1. Middleware Protection (First Line of Defense)
|
|
|
|
**File**: `apps/website/middleware.ts`
|
|
|
|
The middleware provides **server-side** route protection that runs before any page rendering:
|
|
|
|
```typescript
|
|
// Key protection logic
|
|
export function middleware(request: NextRequest) {
|
|
const mode = getAppMode();
|
|
const { pathname } = request.nextUrl;
|
|
|
|
// Public routes are always accessible
|
|
if (isPublicRoute(pathname)) {
|
|
return NextResponse.next();
|
|
}
|
|
|
|
// Check for authentication cookie
|
|
const cookies = request.cookies;
|
|
const hasAuthCookie = cookies.has('gp_session');
|
|
|
|
// In alpha mode, redirect to login if no session
|
|
if (mode === 'alpha' && !hasAuthCookie) {
|
|
const loginUrl = new URL('/auth/login', request.url);
|
|
loginUrl.searchParams.set('returnTo', pathname);
|
|
return NextResponse.redirect(loginUrl);
|
|
}
|
|
|
|
// In pre-launch mode, return 404 for protected routes
|
|
return new NextResponse(null, {
|
|
status: 404,
|
|
statusText: 'Not Found',
|
|
});
|
|
}
|
|
```
|
|
|
|
**Protected Routes** (not in public list):
|
|
- `/dashboard`
|
|
- `/profile/*`
|
|
- `/onboarding`
|
|
- `/sponsor/*`
|
|
- `/admin/*`
|
|
- All other non-public routes
|
|
|
|
**Public Routes**:
|
|
- `/` (home)
|
|
- `/auth/*` (login, signup, etc.)
|
|
- `/api/auth/*` (auth API endpoints)
|
|
|
|
### 2. Component-Level Protection (Second Line of Defense)
|
|
|
|
**Files**: Layout components in protected routes
|
|
|
|
Each protected route group has a layout that wraps content with `AuthGuard`:
|
|
|
|
#### Dashboard Layout
|
|
**File**: `apps/website/app/dashboard/layout.tsx`
|
|
```typescript
|
|
export default function DashboardLayout({ children }: DashboardLayoutProps) {
|
|
return (
|
|
<AuthGuard redirectPath="/auth/login">
|
|
<div className="min-h-screen bg-deep-graphite">
|
|
{children}
|
|
</div>
|
|
</AuthGuard>
|
|
);
|
|
}
|
|
```
|
|
|
|
#### Profile Layout
|
|
**File**: `apps/website/app/profile/layout.tsx`
|
|
```typescript
|
|
export default function ProfileLayout({ children }: ProfileLayoutProps) {
|
|
return (
|
|
<AuthGuard redirectPath="/auth/login">
|
|
<div className="min-h-screen bg-deep-graphite">
|
|
{children}
|
|
</div>
|
|
</AuthGuard>
|
|
);
|
|
}
|
|
```
|
|
|
|
#### Sponsor Layout
|
|
**File**: `apps/website/app/sponsor/layout.tsx`
|
|
```typescript
|
|
export default function SponsorLayout({ children }: SponsorLayoutProps) {
|
|
return (
|
|
<AuthGuard redirectPath="/auth/login">
|
|
<div className="min-h-screen bg-deep-graphite">
|
|
{children}
|
|
</div>
|
|
</AuthGuard>
|
|
);
|
|
}
|
|
```
|
|
|
|
#### Onboarding Layout
|
|
**File**: `apps/website/app/onboarding/layout.tsx`
|
|
```typescript
|
|
export default function OnboardingLayout({ children }: OnboardingLayoutProps) {
|
|
return (
|
|
<AuthGuard redirectPath="/auth/login">
|
|
<div className="min-h-screen bg-deep-graphite">
|
|
{children}
|
|
</div>
|
|
</AuthGuard>
|
|
);
|
|
}
|
|
```
|
|
|
|
#### Admin Layout
|
|
**File**: `apps/website/app/admin/layout.tsx`
|
|
```typescript
|
|
export default function AdminLayout({ children }: AdminLayoutProps) {
|
|
return (
|
|
<RouteGuard config={{ requiredRoles: ['owner', 'admin'] }}>
|
|
<div className="min-h-screen bg-deep-graphite">
|
|
{children}
|
|
</div>
|
|
</RouteGuard>
|
|
);
|
|
}
|
|
```
|
|
|
|
### 3. Page-Level Protection (Third Line of Defense - Defense in Depth)
|
|
|
|
**File**: `apps/website/app/dashboard/page.tsx`
|
|
|
|
The dashboard page includes additional client-side verification:
|
|
|
|
```typescript
|
|
export default function DashboardPage() {
|
|
const router = useRouter();
|
|
const { session, loading: authLoading } = useAuth();
|
|
const { data: dashboardData, isLoading, error } = useDashboardOverview();
|
|
|
|
// Additional client-side auth check (defense in depth)
|
|
useEffect(() => {
|
|
if (!authLoading && !session) {
|
|
router.push('/auth/login?returnTo=/dashboard');
|
|
}
|
|
}, [session, authLoading, router]);
|
|
|
|
// Show loading state during auth check
|
|
if (authLoading) {
|
|
return (
|
|
<main className="min-h-screen bg-deep-graphite flex items-center justify-center">
|
|
<div className="text-white">Verifying authentication...</div>
|
|
</main>
|
|
);
|
|
}
|
|
|
|
// Redirect if not authenticated (should be caught by layout, but this is extra safety)
|
|
if (!session) {
|
|
return null; // Layout will handle redirect
|
|
}
|
|
|
|
// ... rest of dashboard content
|
|
}
|
|
```
|
|
|
|
## Authentication Flow
|
|
|
|
### Unauthenticated User Accessing Protected Route
|
|
|
|
1. **Middleware Intercept**: User requests `/dashboard`
|
|
2. **Cookie Check**: Middleware checks for `gp_session` cookie
|
|
3. **Redirect**: If no cookie, redirect to `/auth/login?returnTo=/dashboard`
|
|
4. **Login**: User authenticates via login flow
|
|
5. **Session Creation**: API creates session, sets `gp_session` cookie
|
|
6. **Return**: User redirected back to `/dashboard`
|
|
7. **AuthGuard**: Layout verifies session exists
|
|
8. **Page Render**: Dashboard content loads
|
|
|
|
### Authenticated User Accessing Protected Route
|
|
|
|
1. **Middleware Intercept**: User requests `/dashboard`
|
|
2. **Cookie Check**: Middleware finds `gp_session` cookie
|
|
3. **Access Granted**: Request proceeds to page rendering
|
|
4. **AuthGuard**: Layout verifies session via AuthContext
|
|
5. **Page Render**: Dashboard content loads
|
|
|
|
### Role-Based Access (Admin Routes)
|
|
|
|
1. **Middleware**: Checks for authentication cookie
|
|
2. **RouteGuard**: Verifies user has required roles (`owner`, `admin`)
|
|
3. **Access**: Only granted if both conditions pass
|
|
|
|
## Security Features
|
|
|
|
### 1. Cookie Security
|
|
- Session cookie: `gp_session`
|
|
- Secure transmission via HTTPS
|
|
- HttpOnly flag (server-side)
|
|
- SameSite policy
|
|
|
|
### 2. Session Validation
|
|
- Client-side session verification via AuthContext
|
|
- Server-side session validation via API
|
|
- Automatic redirect on session loss
|
|
|
|
### 3. Error Handling
|
|
- Loading states during auth verification
|
|
- Graceful redirects for unauthorized access
|
|
- Clear error messages for users
|
|
|
|
### 4. Defense in Depth
|
|
- Multiple protection layers (middleware + layout + page)
|
|
- Each layer independently verifies authentication
|
|
- Reduces single point of failure
|
|
|
|
## Route Protection Matrix
|
|
|
|
| Route Group | Middleware | Layout Guard | Page-Level Check | Role Required |
|
|
|-------------|------------|--------------|------------------|---------------|
|
|
| `/` | ✅ Public | ❌ None | ❌ None | None |
|
|
| `/auth/*` | ✅ Public | ❌ None | ❌ None | None |
|
|
| `/dashboard` | ✅ Protected | ✅ AuthGuard | ✅ Yes | None |
|
|
| `/profile/*` | ✅ Protected | ✅ AuthGuard | ❌ None | None |
|
|
| `/onboarding` | ✅ Protected | ✅ AuthGuard | ❌ None | None |
|
|
| `/sponsor/*` | ✅ Protected | ✅ AuthGuard | ❌ None | None |
|
|
| `/admin/*` | ✅ Protected | ✅ RouteGuard | ❌ None | owner/admin |
|
|
| `/leagues` | ✅ Public | ❌ None | ❌ None | None |
|
|
| `/teams` | ✅ Public | ❌ None | ❌ None | None |
|
|
| `/drivers` | ✅ Public | ❌ None | ❌ None | None |
|
|
| `/leaderboards` | ✅ Public | ❌ None | ❌ None | None |
|
|
| `/races` | ✅ Public | ❌ None | ❌ None | None |
|
|
|
|
## Testing Authentication Protection
|
|
|
|
### Test Scenarios
|
|
|
|
1. **Unauthenticated Access to Protected Route**
|
|
- Navigate to `/dashboard` without login
|
|
- Expected: Redirect to `/auth/login?returnTo=/dashboard`
|
|
|
|
2. **Authenticated Access to Protected Route**
|
|
- Login successfully
|
|
- Navigate to `/dashboard`
|
|
- Expected: Dashboard loads with user data
|
|
|
|
3. **Session Expiry**
|
|
- Login, navigate to `/dashboard`
|
|
- Clear session cookie
|
|
- Expected: Redirect to login on next action
|
|
|
|
4. **Role-Based Access**
|
|
- Non-admin user tries `/admin`
|
|
- Expected: Redirect to login (or 404 in pre-launch mode)
|
|
|
|
### Manual Testing Commands
|
|
|
|
```bash
|
|
# Test 1: Unauthenticated access
|
|
curl -I http://localhost:3000/dashboard
|
|
# Should redirect to /auth/login
|
|
|
|
# Test 2: Check middleware response
|
|
curl -I http://localhost:3000/dashboard -H "Cookie: gp_session=valid_token"
|
|
# Should return 200 OK
|
|
|
|
# Test 3: Check public routes
|
|
curl -I http://localhost:3000/leagues
|
|
# Should return 200 OK (no auth required)
|
|
```
|
|
|
|
## Maintenance Notes
|
|
|
|
### Adding New Protected Routes
|
|
|
|
1. **Add to middleware** if it needs authentication:
|
|
```typescript
|
|
// In isPublicRoute() function
|
|
const publicRoutes = [
|
|
// ... existing routes
|
|
// '/new-public-route', // Add if public
|
|
];
|
|
```
|
|
|
|
2. **Create layout file** for route group:
|
|
```typescript
|
|
// app/new-route/layout.tsx
|
|
export default function NewRouteLayout({ children }) {
|
|
return (
|
|
<AuthGuard redirectPath="/auth/login">
|
|
{children}
|
|
</AuthGuard>
|
|
);
|
|
}
|
|
```
|
|
|
|
3. **Test thoroughly**:
|
|
- Unauthenticated access → redirect
|
|
- Authenticated access → works
|
|
- Session expiry → redirect
|
|
|
|
### Security Best Practices
|
|
|
|
1. **Always use both middleware and component guards**
|
|
2. **Never rely on only one protection layer**
|
|
3. **Test session expiry scenarios**
|
|
4. **Monitor for authentication bypass attempts**
|
|
5. **Keep public routes list minimal**
|
|
|
|
## Current Implementation Status
|
|
|
|
✅ **Complete Protection**:
|
|
- Dashboard route with multi-layer protection
|
|
- Profile routes with AuthGuard
|
|
- Sponsor routes with AuthGuard
|
|
- Admin routes with role-based protection
|
|
- Onboarding routes with AuthGuard
|
|
|
|
✅ **Public Routes**:
|
|
- Home page
|
|
- Auth pages
|
|
- Discovery pages (leagues, teams, drivers, races, leaderboards)
|
|
|
|
✅ **Security Features**:
|
|
- Defense-in-depth architecture
|
|
- Role-based access control
|
|
- Session validation
|
|
- Graceful error handling
|
|
- Clear user feedback
|
|
|
|
✅ **Loading State Fixes**:
|
|
- Fixed infinite loading issue
|
|
- Proper session state management
|
|
- Clear authentication flow feedback
|
|
- No more "stuck on loading" problems
|
|
|
|
## Known Issues Fixed
|
|
|
|
### Loading State Problem (RESOLVED)
|
|
**Issue**: Users experienced infinite "Loading..." state on dashboard
|
|
**Root Cause**: Multiple authentication state systems conflicting
|
|
**Solution**: Unified loading state management across AuthContext, AuthGateway, and RouteGuard
|
|
|
|
**Changes Made**:
|
|
1. AuthContext now properly tracks loading state during session fetch
|
|
2. AuthGateway only uses authContext.loading for isLoading state
|
|
3. AuthorizationBlocker treats null session as unauthenticated (not loading)
|
|
4. RouteGuard provides clear feedback during authentication verification
|
|
5. Dashboard page simplified to remove redundant auth checks
|
|
|
|
**Result**: Authentication flow now works smoothly with proper redirects and no infinite loading.
|
|
|
|
The authentication protection strategy is **comprehensive and secure** for production use. |