'Create a user document onCreate and onUpdate that is in sync with the auth.user Firebase v9

How to securely create a user document onCreate that is in sync with the auth.user database in Firebase v9. I think it wouldn't be secure to let a registered user create a user document. So I wrote a cloud function which triggers on auth.user onCreate functions.auth.user().onCreate(). But as I want my users to input a display name or other information on sign up, I also want them to be stored to both db entries as well. But I have a problem keeping them in sync as I can't await the onCreate() cloud function trigger and so don't know when I can update the user document with all the data that wasn't submitted by user().onCreate() like the displayName and so on. I tried and searched everywhere. I can't believe this is not a standard feature and still a requested topic. This seems so basic.

Error

error FirebaseError: No document to update:

Sign Up function

interface SignUpFormValues {
    email: string;
    password: string;
    confirm: string;
    firstName: string;
    lastName: string;
  }

const handleSignUp = async (values: SignUpFormValues) => {
    const { firstName, lastName, email, password } = values;
    const displayName = `${firstName} ${lastName}`;

    try {
      setError("");
      setLoading(true);
      // Create User
      const userCredential = await createUserWithEmailAndPassword(
        auth,
        values.email,
        values.password
      );
      // -> Signed in

      // Update Profile
      const user = userCredential.user;
      await updateProfile(user, {
        displayName: displayName,
      });
      // IMPORTANT: Force refresh regardless of token expiration
      auth.currentUser.getIdToken(true);

      // Check if user document already exists
      // Get the from cloud functions onCreate created user document -> currently having an error because the doc is no created yet
      const docRef = doc(db, "users", user.uid);
      const docSnap = await getDoc(docRef);
      console.log("docSnap.exists()", docSnap.exists()); // currently false
      console.log("docSnap.data()", docSnap.data());

      // Update user document
      const newUserData = {
        displayName: displayName,
        firstName,
        lastName,
      };
      await updateDoc(docRef, newUserData);

      // Send Email verification
      await authSendEmailVerification(user);

      // Logout
      await logout();

      navigate("/sign-up/email-verification", { state: values });
    } catch (error: any) {
      const errorCode = error.code;
      const errorMessage = error.message;
      console.log("error", error);
      console.log("error", errorCode);
      if (errorCode === "auth/email-already-in-use") {
        const errorMessage =
          "Failed to create an account. E-Mail address is already registered.";
        setError(errorMessage);
        console.log("error", errorMessage);
      } else {
        setError("Failed to create account.");
      }
    }
    setLoading(false);
  };

Cloud function which triggers the user onCreate

// On auth user create
export const authUserWriteListener = functions.auth
  .user()
  .onCreate(async (user, context) => {
    console.log("user:", user);
    const userRef = db.doc(`users/${user.uid}`);
    await userRef.set({
      email: user.email,
      createdAt: context.timestamp,
      firstTimeLogin: true,
    });

    return db.doc("stats/users").update({
      totalDocsCount: FieldValue.increment(1),
    });
  });


Solution 1:[1]

I think it wouldn't be secure to let a registered user create a user document.

Why not? Security rules can be used so the user can update their own data only. So you can run updateProfile and updateDoc right after createUserWithEmailAndPassword.

I have a problem keeping them in sync as I can't await the onCreate() cloud function trigger

If you still want to use Cloud functions, then you can use callable functions and create user account, update all the required information in it and then sign user in with client SDK. So on client side:

import { getFunctions, httpsCallable } from "firebase/functions";

const functions = getFunctions();
const createNewUser = httpsCallable(functions, 'createNewUser');

createNewUser({ email, password, displaName, ...otherData })
  .then((result) => {
    // Read result of the Cloud Function. (user account is created)
    // Sign user in with client SDK 
  });

Also checkout:

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Dharmaraj