Search code examples
reactjsfirebasegoogle-cloud-platformgoogle-cloud-firestorefirebase-authentication

firebase, linkWithCredential to add new auth provider to an Account


I'm building a registration component in a React app using Firebase Authentication and Firestore. I want to implement the following functionality:

If a user tries to register with Google and they already exist in Firestore, they should be redirected to the homepage. If they do not exist, I want to create a new document in Firestore. If the user already has an account with an email/password, I want to link the Google account as an additional provider.

I'm encountering an issue, when I test it, if the user has an email/password provider, it will erase that and only add the Google provider.

const handleEmailPasswordRegistration = async (e) => {
    e.preventDefault();
    setEmailloading(true);
    setMessage({ type: "", text: "" });

    try {
      const userCredential = await createUserWithEmailAndPassword(
        auth,
        email,
        password
      );
      const user = userCredential.user;

      await setDoc(doc(db, "users", user.uid), {
        firstName,
        lastName,
        email: user.email,
        createdAt: new Date(),
      });

      setMessage({ type: "success", text: "Registration successful!" });
      navigate("/");
    } catch (error) {
      console.error(error);
      setMessage({ type: "error", text: error.message });
    } finally {
      setEmailloading(false);
    }
  };

  const handleGoogleRegistration = async () => {
    setGoogleloading(true);
    setMessage({ type: "", text: "" });

    try {
      const result = await signInWithPopup(auth, googleProvider);
      const user = result.user;

      const userRef = doc(db, "users", user.uid);
      const docSnap = await getDoc(userRef);

      if (docSnap.exists()) {
        if (
          user.providerData.some(
            (provider) => provider.providerId === "google.com"
          )
        ) {
          setMessage({
            type: "success",
            text: "You are already registered with Google!",
          });
        } else {
          const credential = GoogleAuthProvider.credentialFromResult(result);
          await linkWithCredential(user, credential);
          setMessage({
            type: "success",
            text: "Google account successfully linked to your existing account!",
          });
        }
      } else {
        await setDoc(doc(db, "users", user.uid), {
          firstName: user.displayName?.split(" ")[0] || "",
          lastName: user.displayName?.split(" ")[1] || "",
          email: user.email,
          createdAt: new Date(),
        });

        setMessage({
          type: "success",
          text: "Registration successful with Google!",
        });
      }

      navigate("/");
    } catch (error) {
      console.error(error);
      setMessage({ type: "error", text: error.message });
    } finally {
      setGoogleloading(false);
    }
  };

I'm struggling with how to properly check for user existence in Firestore and link additional (google) authentication providers without overwriting existing ones(email/password).

I used linkWithCredentials and also linkWithPopUp but it still erases and adds the Google provider as the auth provider.


Solution

  • It doesn't overwrite the user, it overwrites the user auth provider. If the user is registered using email/password then logs out and tries to log in by using their Google account, it will erase the email and password and use the Google auth as a provider.

    What you're experiencing, is the expected behavior because when the user signs in with email and password and right after with the Google provider, the data is always overwritten in the Firebase Authentication. This means that Google credentials remain the default and the email and password that were chosen before will no longer work to authenticate users. This is happening for an obvious reason, a Google account is a more trusted source than any other provider, including email and password.

    If you want to make both authentication types work, I recommend you link those 2 accounts in a single one, based on the email address.