5.2 KiB
OAuth User Synchronization Fix
Problem
When a user signs on with an OAuth provider (Microsoft Entra/Azure AD) for the first time, the user is added to auth.users in Supabase but NOT to the application's user_profiles table. This causes the application to fail when trying to load user data, as there's no user profile available.
Solution
Implemented automatic user profile creation for OAuth users through a multi-layered approach:
1. Database Trigger (00003_oauth_user_sync.sql)
-
Location:
supabase/migrations/00003_oauth_user_sync.sqlandmanagement-dashboard-web-app/supabase/migrations/00003_oauth_user_sync.sql -
Function:
handle_new_oauth_user()- Automatically creates a user profile in
public.user_profileswhen a new user is created inauth.users - Handles the synchronization at the database level, ensuring it works regardless of where users are created
- Includes race condition handling with
ON CONFLICT DO NOTHING
- Automatically creates a user profile in
-
Trigger:
on_auth_user_created- Fires after INSERT on
auth.users - Ensures every new OAuth user gets a profile entry
- Fires after INSERT on
2. Client-Side Utility Function (src/lib/supabase.ts)
- Function:
userManagement.syncOAuthUser()- Provides a fallback synchronization mechanism for any OAuth users that slip through
- Checks if user profile exists before creating
- Handles race conditions gracefully (duplicate key errors)
- Includes comprehensive error logging for debugging
Logic:
1. Get current authenticated user from Supabase Auth
2. Check if user profile already exists in user_profiles table
3. If exists: return (no action needed)
4. If not exists: create user profile with:
- id: user's UUID from auth.users
- email: user's email from auth.users
- status: 'active' (default status)
5. Handle errors gracefully:
- "No rows returned" (PGRST116): Expected, user doesn't exist yet
- "Duplicate key" (23505): Race condition, another process created it first
- Other errors: Log and continue
3. App Integration (src/App.tsx)
- Updated: Auth state change listener
- Triggers: When
SIGNED_INorINITIAL_SESSIONevent occurs - Action: Calls
userManagement.syncOAuthUser()asynchronously - Benefit: Ensures user profile exists before the rest of the app tries to access it
How It Works
OAuth Sign-In Flow (New)
1. User clicks "Sign in with Microsoft"
↓
2. Redirected to Microsoft login
↓
3. Microsoft authenticates and redirects back
↓
4. Supabase creates entry in auth.users
↓
5. Database trigger fires → user_profiles entry created
↓
6. App receives SIGNED_IN event
↓
7. App calls syncOAuthUser() as extra safety measure
↓
8. User profile is guaranteed to exist
↓
9. getUserProfile() and loadData() succeed
Backward Compatibility
- Non-invasive: The solution uses triggers and utility functions, doesn't modify existing tables
- Graceful degradation: If either layer fails, the other provides a fallback
- No breaking changes: Existing APIs and components remain unchanged
Testing Recommendations
Test 1: First-Time OAuth Sign-In
- Clear browser cookies/session
- Click "Sign in with Microsoft"
- Complete OAuth flow
- Verify:
- User is in Supabase auth
- User is in
user_profilestable - App loads user data without errors
- Dashboard displays correctly
Test 2: Verify Database Trigger
- Directly create a user in
auth.usersvia SQL - Verify that
user_profilesentry is automatically created - Check timestamp to confirm trigger fired
Test 3: Verify Client-Side Fallback
- Manually delete a user's
user_profilesentry - Reload the app
- Verify that
syncOAuthUser()recreates the profile - Check browser console for success logs
Files Modified
-
Database Migrations:
/usda-vision/supabase/migrations/00003_oauth_user_sync.sql(NEW)/usda-vision/management-dashboard-web-app/supabase/migrations/00003_oauth_user_sync.sql(NEW)
-
TypeScript/React:
-
/usda-vision/management-dashboard-web-app/src/lib/supabase.ts(MODIFIED)- Added
syncOAuthUser()method touserManagementobject
- Added
-
/usda-vision/management-dashboard-web-app/src/App.tsx(MODIFIED)- Import
userManagement - Call
syncOAuthUser()on auth state change
- Import
-
Deployment Steps
-
Apply Database Migrations:
# Run the new migration supabase migration up -
Deploy Application Code:
- Push the changes to
src/lib/supabase.tsandsrc/App.tsx - No environment variable changes needed
- No configuration changes needed
- Push the changes to
-
Test in Staging:
- Test OAuth sign-in with a fresh account
- Verify user profile is created
- Check app functionality
-
Monitor in Production:
- Watch browser console for any errors from
syncOAuthUser() - Check database logs to confirm trigger is firing
- Monitor user creation metrics
- Watch browser console for any errors from
Future Enhancements
- Assign default roles to new OAuth users (currently requires manual assignment)
- Pre-populate
first_nameandlast_namefrom OAuth provider data - Add user profile completion workflow for new OAuth users
- Auto-disable account creation for users outside organization