Introduction
Implementing user authentication can be complex, especially when integrating multiple sign-in methods like Google. You may encounter challenges such as handling the flow between creating a new account and logging in, or merging existing users with different sign-in methods.
In this article, I will walk you through code snippet that solves these problems, and we'll also explain how it works so you can better understand how to create a robust authentication process for your own app.
Pre-requisites
Knowledge of React
Basic understanding of Firebase
Problem
So, I was trying to implement Google Signin authentication with the code below:
import { auth } from "../firebase-config";
import { GoogleAuthProvider, signInWithPopup} from "firebase/auth";
import { collection, addDoc, getDocs, doc, query, serverTimestamp} from "firebase/firestore";
const gmailProvider = new GoogleAuthProvider();
const handleUserSignInWithGoogle = async () => {
try {
const res = await signInWithPopup(auth, gmailProvider);
const user = res.user;
const q = query(collection(db, "users"));
const docs = await getDocs(q);
docs.forEach((items) => {
console.log(items);
if (items.data().email == user.email) {
alert("Email already exists, log in instead");
handleNavigateUser("signin");
// return;
} else {
addDoc(collection(db, "users"), {
uid: user.uid,
createdAt: serverTimestamp(),
name: user.displayName,
authProvider: "google",
email: user.email,
password: "",
invoiceData: [],
});
alert("Account created successfully");
handleNavigateUser("create-invoice");
}
});
} catch (error) {
if (error.code == "auth/email-already-in-use") {
alert("The email address is already in use");
} else if (error.code == "auth/invalid-email") {
alert("The email address is not valid.");
} else if (error.code == "auth/operation-not-allowed") {
alert("Operation not allowed.");
} else if (error.code == "auth/weak-password") {
alert("The password is too weak.");
}
}
};
Code explanation
This code is a function that allows a user to sign in with Google using Firebase authentication. The function is triggered when the user clicks a button or takes some other action to initiate the sign-in process.
This code demonstrates the logic behind a Google signin functionality using Firebase as a backend. The GoogleAuthProvider is instantiated to create a new Google authentication provider instance, which is used to authenticate users through their Google accounts.
The handleUserSignInWithGoogle
function serves as main handler for the Google sign-in functionality. When the function is called, it performs the following steps:
Call the
signInWithPopup
method with theauth
andgmailProvider
as its arguments to sign in the user through their Google account.After the user signs in, the user object is stored in the
user
variable.The function then queries the Firebase Firestore database for a collection of users, which is stored in the
docs
variable.The
docs
variable is looped over using theforEach
method. For each document in thedocs
array, the email address is compared to the email address of the user who has just signed in. If the email address already exists, the function alerts the user that the email address is already in use and navigates the user to the login form instead of creating a new account.If the email address does not exist in the
docs
array, the function adds a new document to the Firestore collection of users with the user's information with theaddDoc
method from firestore.The user is then alerted that the account was created successfully and navigated to the invoice creation form.
If an error occurs during the sign-up process, the function checks the error code and alerts the user with a corresponding error message.
Fixing the Signin duplication
import { auth } from "../firebase-config";
import { GoogleAuthProvider, signInWithPopup} from "firebase/auth";
import { collection, addDoc, getDocs, doc, updateDocs, query, serverTimestamp} from "firebase/firestore";
const gmailProvider = new GoogleAuthProvider();
const handleUserSignInWithGoogle = async () => {
try {
// Sign in with Google
const res = await signInWithPopup(auth, gmailProvider);
const googleUser = res.user;
// Check if the Google user email is already in use in Firebase
const q = query(collection(db, "users"));
const docs = await getDocs(q);
let existingUser = null;
docs.forEach((item) => {
if (item.data().email === googleUser.email) {
existingUser = item;
}
});
// If the Google user email is already in use, merge the two users
if (existingUser) {
const userRef = doc(db, "users", existingUser.id);
updateDoc(userRef, {
uid: googleUser.uid,
name: googleUser.displayName,
authProvider: "google",
email: googleUser.email,
});
alert("Login successful");
handleNavigateUser("create-invoice");
} else {
// If the email is not in use, create a new user
addDoc(collection(db, "users"), {
uid: googleUser.uid,
createdAt: serverTimestamp(),
name: googleUser.displayName,
authProvider: "google",
email: googleUser.email,
password: "",
invoiceData: [],
});
alert("Account created successfully");
handleNavigateUser("create-invoice");
}
// await
} catch (error) {
if (error.code == "auth/email-already-in-use") {
alert("The email address is already in use");
} else if (error.code == "auth/invalid-email") {
alert("The email address is not valid.");
} else if (error.code == "auth/operation-not-allowed") {
alert("Operation not allowed.");
} else if (error.code == "auth/weak-password") {
alert("The password is too weak.");
}
}
};
Code explanation
First, the function creates a new instance of the
GoogleAuthProvider
class, which allows the user to sign in with their Google credentials. Then the function uses thesignInWithPopup
method from Firebase authentication to sign in the user.After the user has been signed in, the function retrieves all the documents from the
users
collection in Cloud Firestore.It then loops through each document, checking if any of them have the same email address as the signed-in Google user.
If a document with the same email address is found, the function updates the corresponding Firestore document with the new user information with the
updateDoc()
method from firebase.If no document with the same email address is found, the function creates a new document in the
users
collection with the information of the signed-in Google user.
Solution
Compared to the first code, this code checks if the signed-in Google user email address already exists in the Firestore database before creating a new document. If the email address is already in use, the function updates the corresponding Firestore document with the new user information with the updateDoc
functionality, rather than creating a new document and overwriting the existing data. This way, the data in the Firestore database is preserved and not overridden.
Also, note that not all the user fields are being updated in the updateDoc
function such as the invoiceData
array. This is to ensure that the existing data in the users field do not get overridden by the updateDoc when they are signing in with Google.
Conclusion
I hope this code has been helpful to you and provides a good solution to integrating Google SignIn with Firebase. Follow me here on Hashnode for more helpful articles and tips on coding.