Users duplicate on Firestore when I use googleSignIn - javascript

I have two ways to register a user in Firebase: through email and through Google Sign In.
I perform the user registration by email as follows:
signUp() {
const auth = getAuth();
const db = getFirestore();
createUserWithEmailAndPassword(
auth,
this.createEmail,
this.createPassword
).then(
(userCredential) => {
const user = userCredential.user;
this.$router.push("/");
addDoc(collection(db, "users"), {
email: this.createEmail,
name: this.createName,
});
},
);
},
In other words, in addition to saving the user in Firebase Authentication, I also send their name and email to Firestore. And this is my first question:
Is it the most effective way to save the username and future data that will still be added to it?
Finally, login by Google is done as follows:
googleSignIn() {
const auth = getAuth();
const provider = new GoogleAuthProvider();
signInWithPopup(auth, provider)
.then((result) => {
this.$router.push("/");
addDoc(collection(db, "users"), {
email: result.user.email,
name: result.user.displayName,
});
})
},
Here a problem arises because if a user logs in more than once in Firebase Authentication everything is ok, but in Firebase Firestore a user is created for each new login with Google.
How do I handle this issue of storing users in Firestore, especially users coming from Google Login?

First, I'd move the router.push() statement below addDoc() so I can confirm that the document has been added and then user is redirected to other pages. In case of Google SignIn, you can check if the user is new by accessing the isNewUser property by fetching additional information. If true, then add document to Firestore else redirect to dashboard:
signInWithPopup(auth, provider)
.then(async (result) => {
// Check if user is new
const {isNewUser} = getAdditionalUserInfo(result)
if (isNewUser) {
await addDoc(collection(db, "users"), {
email: result.user.email,
name: result.user.displayName,
});
}
this.$router.push("/");
})
It might be a good idea to set the document ID as user's Firebase Auth UID instead of using addDoc() which generated another random ID so it's easier to write security rules. Try refactoring the code to this:
signInWithPopup(auth, provider)
.then(async (result) => {
// Check if user is new
const {isNewUser} = getAdditionalUserInfo(result)
const userId = result.user.uid
if (isNewUser) {
await setDoc(doc(db, "users", userId), {
email: result.user.email,
name: result.user.displayName,
});
}
this.$router.push("/");
})

Related

How to await the creation of a document created in cloud functions with onSnapshot

I want 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 functions.auth.user().onCreate() and creates a user document.
Problem:
I have the problem keeping them in sync as the onSnapshotmethod which should await for the user document to exists already returns a promise if the user document does not yet exists. Sometimes it works and sometimes not. So I don't know when I can update the by the cloud function created user document.
Question:
Why does the onSnapshot sometimes work and sometimes not. How can I fix it?
Here is a link to a helpful Article which seem to doesn't work in v9. Link
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: as const user = await createAccount(displayName, email, password); returns even if user is not yet in doc.data()
Sign Up function
interface SignUpFormValues {
email: string;
password: string;
confirm: string;
firstName: string;
lastName: string;
}
const createAccount = async (
displayName: string,
email: string,
password: string
) => {
// Create auth user
const userCredential = await createUserWithEmailAndPassword(
auth,
email,
password
);
// -> Signed in
// Update Profile
const user = userCredential.user;
const uid = user.uid;
await updateProfile(user, {
displayName: displayName,
});
// IMPORTANT: Force refresh regardless of token expiration
// auth.currentUser.getIdToken(true); // -> will stop the onSnapshot function from resolving properly
// Build a reference to their per-user document
const userDocRef = doc(db, "users", uid);
return new Promise((resolve, reject) => {
const unsubscribe = onSnapshot(userDocRef, {
next: (doc) => {
unsubscribe();
console.log("doc", doc); // -> returning undefined
console.log("doc.data()", doc.data()); // -> returning undefined
resolve(user); // -> returning undefined
},
error: (error) => {
unsubscribe();
console.log("error", error);
reject(error);
},
});
});
};
const handleSignUp = async (values: SignUpFormValues) => {
const { firstName, lastName, email, password } = values;
const displayName = `${firstName} ${lastName}`;
try {
setError("");
setLoading(true);
// Create user account
const user = await createAccount(displayName, email, password);
console.log("createAccount -> return:", user); // -> problem here sometimes return undefined
// Update user
const newUserData = {
displayName: displayName,
firstName,
lastName,
};
// Build a reference to their per-user document
const userDocRef = doc(db, "users", user.uid);
await updateDoc(userDocRef, 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),
});
});
The issue is that the Cloud Function code runs asynchronously. There is no guarantee that it will run quickly enough to have the document created in Firestore between the end of createAccount() and your call to updateDoc(). In fact, if your system has been idle for a while it could be a minute (or more!) for the Cloud Function to execute (do a search for "cold start firebase cloud functions").
One option, depending on your design, might be to not take in first name and last name during sign up? But instead take the user to a "profile page" once they are logged in where they could modify aspects of their profile (by that time the user profile document hopefully is created). On that page, if the get() returns no document, you could put up a notification to the user that the system "is still processing their registration" or something like that.

How to detect whether google auth login user is a NewUser or not using firebase in react native

How to detect the login user is an existing user or a new user using firebase in react native. I have used Google auth to create the authentication but unfortunately I am not getting any field called isNewUser in return promise.
below is my code...
async function onGoogleButtonPress() {
// Get the users ID token
const {idToken} = await GoogleSignin.signIn();
// Create a Google credential with the token
const googleCredential = auth.GoogleAuthProvider.credential(idToken);
// Sign-in the user with the credential
return auth().signInWithCredential(googleCredential);
}
function onAuthStateChanged(user) {
if (user) {
firestore()
.collection('Users')
.doc(user.uid)
.set({
user: user.displayName,
email: user.email,
photo: user.photoURL,
})
.then(() => {
console.log('User added!');
});
}
if (initializing) setInitializing(false);
}
useEffect(() => {
const subscriber = auth().onAuthStateChanged(onAuthStateChanged);
return subscriber; // unsubscribe on unmount
});
This is the return response I am getting.
{"displayName": "***", "email": "**#gmail.com", "emailVerified": true, "isAnonymous": false, "metadata": {"creationTime": 15960**412290, "lastSignInTime": 15960**65185}, "phoneNumber": null, "photoURL": "**", "providerData": [[Object]], "providerId": "firebase", "uid": "*******"}
My problem now is everytime once user successfully authenticate google signin method its adding data's to the firebase data base. Does there any way that I can detect the user is a new user or a existing user??
A help will be great and appreciable :)
The isNewUser property is in the UserCredential object, which is only available right after the call to signInWithCredential.
const credentialPromise = auth().signInWithCredential(googleCredential);
credentialPromise.then((credential) => {
console.log(credential.additionalUserInfo.isNewUser);
})
You can determine whether the user is new from the auth state listener, by comparing the user's creation timestamp to their last sign in:
function onAuthStateChanged(user) {
if (user) {
if (user.metadata.creationTime <> user.metadata.lastSignInTime) {
...
}
}
}
Also see:
the documentation for AdditionalUserInfo.
the documentation for UserMetadata.

Can't add user to firestore database after signing up with firebase

I want to add a user to a firestore database after signing up through firebase with email and password. I get no errors, and the user is created and is visible in firebase auth. However the user is not added to the firestore database.
Does anyone know how to make this work? My code snippet is pasted below.
firebase.initializeApp(config);
const auth = firebase.auth();
const db = firebase.firestore();
auth.createUserWithEmailAndPassword(email, pass).then(cred => {
const userId = cred.user.uid;
const userData = {
firstName: firstName,
lastName: lastName,
email: email
};
db.collection("users").doc(userId).set(userData).then(() => {
console.log("User successfully added to the DB!");
})
.catch((e) => {
console.log("Error adding user to the DB: ", e);
});
}).then(() => {
console.log('User created! - ' + email);
}).catch(e => {
console.log(e.message);
txtErrMsg.classList.remove('hide');
txtErrMsg.innerHTML = e.message;
});
FYI: I have console logged the parameters (userId and userData), and they appear just fine.
Thanks!
I figured out what was wrong with my code.
I wrote another method auth.onAuthStateChanged() which was interfering with my action to write to the database. Since I have two auth files (signup.js and login.js), I decided to only keep this method in login.js and to remove it from signup.js.
Now I can successfully add the user to the DB upon signup (signup.js), and I don't need to add anything to the DB when a user is simply logging in (login.js).
I hope this helps anyone out there experiencing the same problem.
I experienced that problem myself. The only way I managed to get this working was to await auth first, and then add the user to firestore:
I have a firebaseConfig.js where I set up the usersCollection:
import firebase from 'firebase';
import 'firebase/firestore';
const firebaseConfig = {
apiKey: ......
};
firebase.initializeApp(firebaseConfig);
const auth = firebase.auth();
const db = firebase.firestore();
const usersCollection = db.collection('users');
export { auth, usersCollection };
Then, I use it like this.
const fb = require('../firebaseConfig.js');
....
const cred = await fb.auth
.createUserWithEmailAndPassword(
'someemail#test.com',
'password123'
)
.catch(e => {
console.log(e.message);
});
if (cred) {
console.log({ cred });
const userId = cred.user.uid;
const userData = {
firstName: 'firstName',
lastName: 'lastName',
email: 'email',
};
fb.usersCollection
.doc(userId)
.set(userData)
.then(() => {
console.log('User successfully added to the DB!');
})
.then(() => {
console.log('User created!);
})
.catch(e => {
console.log('Error adding user to the DB: ', e);
});
}
When I ran this code I got the following in my log:
And it appears in my database like this:

firebase/firestore save user to collection

I am trying to save a user to a collection in my Firestore. It seems that the users are being created, I can see them in the Authentication tab, however they are not saving to my collection inside my firestore. There doesn't seem to be any errors inside my console either. I've been struggling with this for a couple hours now and i'm not even sure how to debug this.
export const authMethods = {
signup: (email, password, setErrors, setToken) => {
firebase
.auth()
.createUserWithEmailAndPassword(email, password)
// make res asynchronous so that we can make grab the token before saving it.
.then(async res => {
const token = await Object.entries(res.user)[5][1].b
// set token to localStorage
await localStorage.setItem('token', token)
// grab token from local storage and set to state.
setToken(window.localStorage.token)
const userUid = firebase.auth().currentUser.uid
const db = firebase.firestore()
db.collection('/users')
.doc(userUid)
.set({
email,
password,
})
console.log(res)
})
.catch(err => {
setErrors(prev => [...prev, err.message])
})
},
....
}
Any ideas?
Remove await from localStorage.setItem it isn't an asynchronous function.
You'll also need to add await to db.collection("/users").doc(userUid)
This is another approach that you could do. Let a cloud function handle that for you.
Whenever a user is created the following function is triggered.
export const onUserCreate = functions.auth
.user()
.onCreate(async (user, context) => {
await admin.firestore().collection("users").doc(user.uid).set({
id: user.uid,
emailAddress: user.email,
verified: user.emailVerified,
});
});
If you need more information about cloud functions read the following.
https://firebase.google.com/docs/functions/get-started

Firebase Auth state changes after creating new user

Auth state changes when a new user is created with email and password.
I implement firebase.auth().onAuthStateChanged() observable to watch login state in my app. But it has a tool for creating new users that reproduces the issue. After creating new user withfirebase.auth().createUserWithEmailAndPassword() the observable returns the new user, which causes my app to log out.
Is this normal? How can I create new users from my app without changing auth state?
See the stackblitz example
while creating the user using firebase.auth().createUserWithEmailAndPassword() it will automatically logged out the current user and will logged into the newly created user.To avoid this you have to create the new user using admin sdk.
here is the sample code:
exports.createUser = functions.firestore
.document('user/{userId}')
.onCreate(async (snap, context) => {
try {
const userId = snap.id;
const batch = admin.firestore().batch();
const newUser = await admin.auth().createUser({
disabled: false,
displayName: snap.get('name'),
email: snap.get('email'),
password: snap.get('password')
});
const ref1 = await
admin.firestore().collection('user').doc(newUser.uid);
await batch.set(ref1, {
id: newUser.uid,
email: newUser.email,
name: newUser.displayName,
createdAt: admin.firestore.FieldValue.serverTimestamp()
});
const ref3 = await admin.firestore().collection('user').doc(userId);
await batch.delete(ref3);
return await batch.commit();
}
catch (error) {
console.error(error);
}
});

Categories