0

I'm experiencing a critical issue with my NextJS/Firebase application where approximately 40% of random user registrations result in "orphaned" users - users that exist in Firebase Authentication but have no corresponding document in Firestore.

The issue occurs randomly with no consistent pattern or error messages:

  1. Users are 100% successfully created in Firebase Authentication
  2. But approximately 40% of random registration attempts fail to create the Firestore document
  3. No errors are thrown during this process
  4. Affected users see a "User not approved" error on our Login form
  5. These users can't get their pending registrations approved from the admin side because our app checks for Firestore documents

Here's my user registration function in AuthContext.js:

// New user registration
const register = async (email, password, userData) => {
  setErrorMessage('');
  let user = null;
  
  try {
    setLoading(true);
    
    // 1. Create user in Firebase Auth
    const userCredential = await createUserWithEmailAndPassword(auth, email, password);
    user = userCredential.user;
    
    // 2. Create user document data
    const userDocData = {
      email: email,
      name: userData.name || '',
      surname: userData.surname || '',
      phone: userData.phone || '',
      role: userData.role || 'student',
      status: 'pending',
      createdAt: serverTimestamp(),
      updatedAt: serverTimestamp(),
      package: userData.role === 'student' ? {
        remainingClasses: 0,
        paymentStatus: false
      } : null
    };
    
    // 3. Create document in Firestore
    const batch = writeBatch(db);
    batch.set(doc(db, 'users', user.uid), userDocData);
    await batch.commit();
    
    // 4. Verify document creation
    const checkDoc = await getDoc(doc(db, 'users', user.uid));
    if (!checkDoc.exists()) {
      throw new Error('User document was not created');
    }
    
    // 5. Logout after successful registration
    await signOut(auth);
    
    return user;
    
  } catch (error) {
    // If Auth user created but Firestore document failed, delete Auth user
    if (user) {
      try {
        // Try to delete any partially created document
        try {
          await deleteDoc(doc(db, 'users', user.uid));
        } catch (docError) {
          console.error("Error deleting document:", docError);
        }
        
        // Delete Auth user
        await deleteUser(user);
      } catch (deleteError) {
        console.error('Error deleting user after failed registration:', deleteError);
      }
    }
    
    // Handle specific errors
    switch (error.code) {
      case 'auth/email-already-in-use':
        setErrorMessage('Αυτή η διεύθυνση email χρησιμοποιείται ήδη.');
        break;
      case 'auth/invalid-email':
        setErrorMessage('Μη έγκυρη διεύθυνση email.');
        break;
      case 'auth/weak-password':
        setErrorMessage('Ο κωδικός πρέπει να έχει τουλάχιστον 6 χαρακτήρες.');
        break;
      default:
        setErrorMessage('Σφάλμα κατά την εγγραφή. Παρακαλώ δοκιμάστε ξανά.');
        break;
    }
    throw error;
  } finally {
    setLoading(false);
  }
};

I've also tried implementing a retry mechanism with exponential backoff:

// 3. Create document with retry mechanism
let success = false;
let attempts = 0;

while (!success && attempts < 5) {
  try {
    attempts++;
    console.log(`Attempt ${attempts} to create Firestore document`);
    
    const batch = writeBatch(db);
    batch.set(doc(db, 'users', user.uid), userDocData);
    await batch.commit();
    
    // Immediate verification
    const checkDoc = await getDoc(doc(db, 'users', user.uid));
    if (checkDoc.exists()) {
      success = true;
      console.log(`Successfully created Firestore document on attempt ${attempts}`);
    } else {
      console.warn(`Document check failed after batch commit (attempt ${attempts})`);
      // Wait before retrying (exponential backoff)
      await new Promise(resolve => setTimeout(resolve, 1000 * attempts));
    }
  } catch (error) {
    console.error(`Firestore error on attempt ${attempts}:`, error);
    // Wait before retrying
    await new Promise(resolve => setTimeout(resolve, 1000 * attempts));
  }
}

if (!success) {
  throw new Error(`Failed to create Firestore document after ${attempts} attempts`);
}

I've also implemented a temporary workaround in onAuthStateChanged:

useEffect(() => {
  const unsubscribe = onAuthStateChanged(auth, async (user) => {
    if (user) {
      try {
        // Check if document exists
        const userDoc = await getDoc(doc(db, 'users', user.uid));
        
        // If document doesn't exist, create it (fix orphaned users)
        if (!userDoc.exists()) {
          console.log("Orphaned user detected. Synchronizing...");
          
          await setDoc(doc(db, 'users', user.uid), {
            email: user.email,
            name: '',
            surname: '',
            phone: '',
            role: 'student',
            status: 'pending',
            createdAt: serverTimestamp(),
            updatedAt: serverTimestamp(),
            package: {
              remainingClasses: 0,
              paymentStatus: false
            }
          });
          
          // Continue with normal checks...
        }
      } catch (error) {
        console.error('Error fetching user data:', error);
        setCurrentUser(null);
      }
    } else {
      setCurrentUser(null);
    }
    setLoading(false);
  });

  return () => unsubscribe();
}, [auth, db, router]);

What I've Tried

  1. Using writeBatch instead of setDoc - same issue occurs
  2. Adding a retry mechanism with up to 5 attempts - still fails occasionally
  3. Adding verification after document creation - confirms document isn't being created
  4. Adding proper cleanup to delete orphaned Authentication users
  5. Adding detailed logging - no errors appear in logs when the issue occurs

Environment

  • NextJS 14
  • Firebase v9
  • Using the client SDK
  • Using the initialization pattern with singleton instance

What could be causing this inconsistent behavior where Firebase Auth users are created but Firestore documents fail silently ~40% of the time? I need a reliable solution as this affects a significant portion of our user registrations.

Is there a way to ensure atomicity between Auth and Firestore operations? Would using Firebase Cloud Functions for user creation provide better reliability?

6
  • “Would using Firebase Cloud Functions for user creation provide better reliability?” The short version of the answer is: yes, do this. Commented Sep 9 at 4:06
  • 1
    1. Are there security rules that govern the creation of documents in the users collection? 2. What happens if your code sleeps for a few seconds after writing the document but before reading it back? Commented Sep 9 at 13:10
  • 1
    Also, consider that onAuthStateChanged might actually be the right way to do something immediately after the user is signed in (and not a workaround as you suggest). Commented Sep 9 at 13:15
  • Thank you both for your questions and advice! They were really enlightening. @DougStevenson Regarding security rules: I have the default rules that allow unrestricted access: rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read, write: if request.time < timestamp.date(2025, 9, 12); } } } So it doesn't seem to be a security restriction issue. Commented Sep 10 at 1:59
  • @DougStevenson Regarding the delay: I implemented your suggestion to add a delay after batch.commit() and before document verification. I tried a delay of 2-3 seconds (increasing with each iteration), but the problem still occurs. I noticed that it happens randomly—sometimes it works correctly and sometimes it doesn't, with no obvious pattern. Regarding onAuthStateChanged: I agree with your comment! I have already implemented a mechanism in onAuthStateChanged to check and repair "orphaned" users. Initially, I considered it temporary, but with your comment seems as a logical part of the code Commented Sep 10 at 2:01

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.