0

With latest version of Firebase Javascript SDK (10.1.0) I am using signInWithPopup to authenticate users through Providers and then saving their credentials inside the DB:

  userCredentials = await signInWithPopup(auth, new GoogleAuthProvider());
  await setDoc(
    doc(db, "users", userCredentials.user.uid),
    {
      uid: userCredentials.user.uid,
      email: userCredentials.user.email,
      ...,
    },
    { merge: true },
  );

How can I know if the recently set document was created (user was authenticated for the first time, i.e. signed up to the service) or updated (user was already registered)?

PS: I would like to avoid querying the DB to check for existing email, as I would need to change the access rules and make all users data available to anyone.

Renaud Tarnec
  • 79,263
  • 10
  • 95
  • 121
Marco Gagliardi
  • 565
  • 1
  • 8
  • 24

3 Answers3

1

I normally used getDoc for such use cases. I call it before setDoc. Based on your example code I would adapt my code like this:

const userDocRef = doc(db, "users", userCredentials.user.uid);
const userDocSnapshot = await getDoc(userDocRef);

if (userDocSnapshot.exists()) {
  // Document exists, so the user was already registered
  await updateDoc(userDocRef, {
    // fields to update
  });
} else {
  // Document does not exist, so the user is signing up for the first time
  await setDoc(userDocRef, {
    uid: userCredentials.user.uid,
    email: userCredentials.user.email,
    // other fields
  });
}

Let me know if something is unclear or bad commented and everyone feel free to edit.

MaikeruDev
  • 1,075
  • 1
  • 19
  • The problem with this approach (that is the mos similar one to a typical backend logic) is that you need to change the access rules in the Firestore configuration to allow anyone read the users collection – Marco Gagliardi Aug 11 '23 at 14:00
1

The only way to know if a Firestore document is present in the database is to query it.

As a workaround you could use the updateDoc() method to write to the DB and in case the doc does not already exist it will return an error of type FirebaseError: No document to update and you would then use the setDoc() method in a catch() block to create it.

BUT note that this approach costs a write each time the doc does not exist. It's up to you to do the math to calculate the best option for you (pay for a read each time you want to know if the doc exists or pay for an extra write when you actually create the doc).

Renaud Tarnec
  • 79,263
  • 10
  • 95
  • 121
0

The point at the root of the question is security: in order to query the db and know if an email (or a username) already exists, you have to change the Firestore's security rules and give read access to the users collection to anyone:

match /users/{uid} {
      allow read, write: if true

❌ this is obviously not acceptable as it exposes the system to unauthorised information disclosure

In this post the problem is well summarised and a solution is provided (disabling client access to firestore and passing through a backend layer), however this solution makes you loose most of the benefit of using firestore as a client-side library and firebase as a server-less architecture.

✅ I have finally managed to solve the problem with a simple - yet safe - workaround. I have changed the rule as follows:

match /users/{uid} {
    allow read, write: if request.auth.uid == uid || resource.data.email == request.auth.token.email;

In this way, after a user has authenticated through an Auth Provider (e.g. Google) I can query the DB to check if there is already a record with that email.

➕ As an extra bonus, I can also handle accounts reconciliation when the same user (i.e. the same email) authenticates through different Providers (e.g. Google and Linkedin)

Marco Gagliardi
  • 565
  • 1
  • 8
  • 24