# Quick Reference: Clean Authentication & Authorization

## The Golden Rules

1. **API is the source of truth** - Never trust the client for security
2. **Client is UX only** - Redirect, show loading, hide buttons
3. **One clear flow** - Middleware → API → Guard → Controller
4. **Roles are server-side** - Client only knows "can access" or "can't"

## What Goes Where

### Server-Side (API)
```typescript
// ✅ DO: Check permissions
@RequireRoles('admin')
@Get('users')
getUsers() { ... }

// ✅ DO: Return 401/403
throw new UnauthorizedException('Auth required')
throw new ForbiddenException('No permission')

// ❌ DON'T: Redirect
res.redirect('/login') // Never do this

// ❌ DON'T: Trust client identity
const userId = req.body.userId // Wrong!
const userId = req.user.userId // Correct
```

### Client-Side (Website)
```typescript
// ✅ DO: Redirect unauthenticated users
if (!session && !loading) {
  router.push('/auth/login')
}

// ✅ DO: Show loading states
if (loading) return <Loading />

// ✅ DO: Hide UI elements
{canAccess && <button>Delete</button>}

// ❌ DON'T: Make security decisions
if (user.role === 'admin') // Wrong! API decides

// ❌ DON'T: Trust your own checks
// Client checks are UX only, API is the gatekeeper
```

## Route Protection Patterns

### Public Route
```typescript
// app/leagues/page.tsx
export default function LeaguesPage() {
  return <LeaguesList />;
}
// No protection needed - accessible by all
```

### Authenticated Route
```typescript
// app/dashboard/layout.tsx
import { AuthLayout } from '@/lib/guards/AuthLayout';

export default function DashboardLayout({ children }) {
  return <AuthLayout>{children}</AuthLayout>;
}

// app/dashboard/page.tsx
export default function DashboardPage() {
  return <DashboardContent />;
}
// Layout handles auth check, page is clean
```

### Role-Protected Route
```typescript
// app/admin/layout.tsx
import { RoleLayout } from '@/lib/guards/RoleLayout';

export default function AdminLayout({ children }) {
  return (
    <RoleLayout requiredRoles={['owner', 'admin']}>
      {children}
    </RoleLayout>
  );
}

// app/admin/page.tsx
export default function AdminPage() {
  return <AdminDashboard />;
}
// Layout handles role check
```

### Scoped Route (League Admin)
```typescript
// app/leagues/[id]/settings/layout.tsx
import { AuthLayout } from '@/lib/guards/AuthLayout';
import { LeagueAccessGuard } from '@/components/leagues/LeagueAccessGuard';

export default function LeagueSettingsLayout({ children, params }) {
  return (
    <AuthLayout>
      <LeagueAccessGuard leagueId={params.id}>
        {children}
      </LeagueAccessGuard>
    </AuthLayout>
  );
}
// Multiple guards for complex scenarios
```

## API Endpoint Patterns

### Public Endpoint
```typescript
@Public()
@Get('pricing')
getPricing() { ... }
// No auth required
```

### Authenticated Endpoint
```typescript
@RequireAuthenticatedUser()
@Get('profile')
getProfile(@User() user: UserEntity) { ... }
// Any logged-in user
```

### Role-Protected Endpoint
```typescript
@RequireRoles('admin')
@Get('users')
getUsers() { ... }
// Only admins
```

### Scoped Endpoint
```typescript
@RequireAuthenticatedUser()
@Get('leagues/:leagueId/admin')
getLeagueAdmin(
  @Param('leagueId') leagueId: string,
  @User() user: UserEntity
) {
  // Check if user is league admin
  this.leagueService.verifyLeagueAdmin(leagueId, user.id);
  ...
}
// Check scope in service
```

## Error Handling

### API Returns
- **401 Unauthorized**: No/invalid session
- **403 Forbidden**: Has session but no permission
- **404 Not Found**: Resource doesn't exist OR non-disclosure

### Client Handles
```typescript
try {
  const data = await apiFetch('/api/admin/users');
  return data;
} catch (error) {
  if (error.message.includes('401')) {
    // Redirect to login
    window.location.href = '/auth/login';
  } else if (error.message.includes('403')) {
    // Show access denied
    toast.error('You need admin access');
    router.push('/dashboard');
  } else {
    // Show error
    toast.error(error.message);
  }
}
```

## Common Mistakes

### ❌ Wrong
```typescript
// Client making security decisions
function AdminPage() {
  const { session } = useAuth();
  if (session?.role !== 'admin') return <AccessDenied />;
  return <AdminDashboard />;
}

// API trusting client
@Post('delete')
deleteUser(@Body() body: { userId: string }) {
  const userId = body.userId; // Could be anyone!
  ...
}

// Middleware doing too much
if (user.role === 'admin') { // Wrong place for this!
  return NextResponse.next();
}
```

### ✅ Correct
```typescript
// Client handles UX only
function AdminPage() {
  return (
    <RoleLayout requiredRoles={['admin']}>
      <AdminDashboard />
    </RoleLayout>
  );
}

// API is source of truth
@Post('delete')
@RequireRoles('admin')
deleteUser(@User() user: UserEntity, @Body() body: { userId: string }) {
  // user.id is from session, body.userId is target
  // Service verifies permissions
  ...
}

// Middleware only checks auth
if (!hasAuthCookie) {
  return redirect('/login');
}
// Let API handle roles
```

## Testing Checklist

### Before Deploy
- [ ] Unauthenticated user can't access protected routes
- [ ] Authenticated user can access their routes
- [ ] Wrong role gets redirected/denied
- [ ] Session expiry redirects to login
- [ ] API returns proper 401/403 codes
- [ ] Public routes work without login

### Quick Test Commands
```bash
# Test API directly
curl -I http://localhost:3000/api/admin/users
# Should return 401 (no auth)

# Test with session
curl -I http://localhost:3000/api/admin/users \
  -H "Cookie: gp_session=valid_token"
# Should return 200 or 403 depending on role

# Test public route
curl -I http://localhost:3000/api/leagues/all
# Should return 200
```

## Migration Steps

1. **Simplify middleware** - Remove role logic
2. **Create clean guards** - AuthLayout, RoleLayout
3. **Update layouts** - Replace old RouteGuard
4. **Test all routes** - Check redirects work
5. **Verify API** - All endpoints have proper decorators

## Remember

- **Server**: Security, permissions, data filtering
- **Client**: UX, loading states, redirects
- **Flow**: Always the same, always predictable
- **Debug**: Check each layer in order

**When in doubt**: The API decides. The client just shows what the API says.