## Problem Users experienced infinite login loops where successful authentication would redirect to dashboard, then immediately redirect back to login page. ## Root Cause Client-server authentication mismatch due to httpOnly cookies: - Login API sets httpOnly cookies using server-side Supabase client ✅ - Dashboard server reads httpOnly cookies correctly ✅ - Dashboard client script tried to read httpOnly cookies using client-side Supabase ❌ ## Solution 1. Fixed Admin Dashboard: Removed non-existent `is_super_admin` column references 2. Created Auth Check API: Server-side auth validation for client scripts 3. Updated Admin API Router: Uses auth check API instead of client-side Supabase ## Key Changes - src/pages/admin/dashboard.astro: Fixed database queries - src/pages/api/admin/auth-check.ts: NEW server-side auth validation API - src/lib/admin-api-router.ts: Uses API calls instead of client-side auth - src/pages/api/auth/session.ts: Return 200 status for unauthenticated users - src/pages/login.astro: Enhanced cache clearing and session management ## Testing - Automated Playwright tests validate end-to-end login flow - Manual testing confirms successful login without loops ## Documentation - AUTHENTICATION_FIX.md: Complete technical documentation - CLAUDE.md: Updated with authentication system notes 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
6.8 KiB
Authentication Login Loop Fix
Problem Description
Users experienced a login loop where:
- User enters valid credentials and login succeeds
- User gets redirected to dashboard initially
- Dashboard immediately redirects back to login page
- This creates an infinite loop preventing access to the dashboard
Root Cause Analysis
The issue was a client-server authentication mismatch in the authentication system:
The Problem Flow
- Login API (
/src/pages/api/auth/login.ts): Sets httpOnly cookies using server-side Supabase client ✅ - Dashboard Server (
/src/pages/admin/dashboard.astro): Reads httpOnly cookies using server-side Supabase client ✅ - Dashboard Client Script (
/src/lib/admin-api-router.ts): Attempts to read httpOnly cookies using client-side Supabase client ❌
Technical Details
- httpOnly cookies: Cannot be accessed by client-side JavaScript for security
- Client-side Supabase:
supabase.auth.getSession()fails when cookies are httpOnly - Authentication mismatch: Server says "authenticated" but client says "not authenticated"
Secondary Issues Found
- Missing database column: Admin dashboard tried to select non-existent
is_super_admincolumn - Database query failures: Caused authentication to fail even with valid sessions
Solution Implementation
1. Fixed Admin Dashboard Server-Side Auth
File: /src/pages/admin/dashboard.astro
Problem:
.select('role, organization_id, is_super_admin') // is_super_admin doesn't exist
Fix:
.select('role, organization_id') // Removed non-existent column
2. Created Server-Side Auth Check API
File: /src/pages/api/admin/auth-check.ts (NEW)
Purpose: Provides a server-side authentication check that client-side code can call
Features:
- Uses server-side Supabase client with access to httpOnly cookies
- Returns authentication status and user information
- Handles admin role verification
- Provides consistent error handling
API Response:
{
authenticated: boolean,
isAdmin: boolean,
user?: {
id: string,
email: string,
name: string
},
organizationId?: string,
error?: string
}
3. Updated Admin API Router
File: /src/lib/admin-api-router.ts
Before:
// Tried to use client-side Supabase (fails with httpOnly cookies)
const { data: { session }, error } = await supabase.auth.getSession();
After:
// Uses server-side auth check API
const response = await fetch('/api/admin/auth-check', {
method: 'GET',
credentials: 'include'
});
Testing & Validation
Playwright Automated Testing
Used Playwright to create automated end-to-end tests that:
- Navigate to login page
- Fill credentials and submit form
- Monitor network requests and cookie setting
- Verify final redirect destination
- Capture screenshots at each step
Test Results
- Before Fix: Infinite redirect loop between login and dashboard
- After Fix: Successful login and stable dashboard access
Test Files
/home/tyler/apps/bct-whitelabel/test-login.js: Playwright test script/home/tyler/apps/bct-whitelabel/test-recordings/: Screenshots and recordings
Implementation Details
Authentication Flow (Fixed)
-
User submits login form
- Client sends credentials to
/api/auth/login - Login API validates credentials with Supabase
- Sets httpOnly session cookies
- Returns success + redirect path
- Client sends credentials to
-
User redirected to dashboard
- Server-side auth check reads httpOnly cookies
- Validates session and admin status
- Renders dashboard if authorized
-
Dashboard client script initializes
- Calls
/api/admin/auth-checkendpoint - Server validates httpOnly cookies
- Returns authentication status to client
- Client proceeds with dashboard functionality
- Calls
Security Considerations
- httpOnly cookies: Maintain security by preventing client-side access
- Server-side validation: All authentication checks use server-side Supabase client
- API endpoint security: Auth check API validates session before returning user data
Files Modified
Primary Fixes
-
/src/pages/admin/dashboard.astro- Fixed database query (removed
is_super_admincolumn) - Added proper error handling for user lookup
- Line 20:
select('role, organization_id')instead ofselect('role, organization_id, is_super_admin')
- Fixed database query (removed
-
/src/pages/api/admin/auth-check.ts(NEW)- Created server-side authentication check API
- Handles admin role verification
- Returns user information for client-side use
-
/src/lib/admin-api-router.ts- Replaced client-side Supabase auth with API call
- Line 17-20: Uses
fetch('/api/admin/auth-check')instead ofsupabase.auth.getSession()
Supporting Fixes
-
/src/pages/api/auth/session.ts- Changed unauthenticated response from 401 to 200 status
- Prevents browser console errors for normal "not logged in" state
-
/src/pages/login.astro- Enhanced session cache clearing after successful login
- Reduced cache duration for more responsive auth checks
- Added support for force refresh URL parameters
Monitoring & Maintenance
Key Metrics to Monitor
- Login success rate: Should be near 100% for valid credentials
- Dashboard load time: Should not have authentication delays
- Session API calls: Should not hit rate limits
Future Improvements
- Add
is_super_admincolumn: If super admin functionality is needed - Implement Redis caching: For better session caching in production
- Add authentication middleware: For more centralized auth handling
Troubleshooting
Common Issues
-
"User not authenticated or not admin" error
- Check if user has
adminrole in database - Verify session cookies are being set correctly
- Check if user has
-
404 on
/api/admin/auth-check- Ensure the new API endpoint file was deployed
- Check that the file is in the correct location
-
Still getting login loops
- Clear browser cookies and sessionStorage
- Check if admin dashboard is using the updated admin-api-router
Debug Commands
# Check user role in database
psql -c "SELECT email, role FROM users WHERE email = 'user@example.com';"
# Test auth check API directly
curl -H "Cookie: sb-..." http://localhost:3000/api/admin/auth-check
# Monitor auth-related logs
docker logs bct-astro-dev | grep -E "(LOGIN|AUTH|ADMIN)"
Impact Summary
✅ Fixed: Login loop preventing dashboard access
✅ Improved: Authentication system reliability
✅ Enhanced: Error handling and debugging capabilities
✅ Maintained: Security with httpOnly cookies
✅ Added: Automated testing for authentication flow
The authentication system now works seamlessly across all user types and provides a stable foundation for the application.