Naymur Rahman

Oct 23, 2025 • 7 min read

Firebase Auth Duplicate Email Error: How to Fix It Step-by-Step

Firebase Auth Duplicate Email Error: How to Fix It Step-by-Step

I was building a Next.js app with Firebase Authentication (email/password), and I encountered a frustrating issue: users could sign up multiple times with the same email address, creating duplicate entries in my Firestore database.

Even though Firebase Auth is supposed to prevent duplicate emails automatically, I was still seeing duplicates. So I started researching it and found that this issue is still not resolved. People have created GitHub issues and have been discussing it on Reddit for a long time.

After spending an entire day, I finally managed to resolve the issue in a tricky way. All you have to do is use Firestore Database for this.

Here’s what my original code looked like:

My Original (Problematic) Code:

const createUserProfile = async (user: any) => {
 try {
 // **** // Problem: Using UID as document ID
 const userDocRef = doc(db, "users", user.uid);
 
 const userProfile = {
 uid: user.uid,
 email: user.email,
 userType: "staff",
 createdAt: serverTimestamp(),
 updatedAt: serverTimestamp(),
 };

 await setDoc(userDocRef, userProfile);
 console.log("User profile created");
 } catch (error) {
 console.error("Error creating user profile:", error);
 throw error;
 }
};

const handleSignUp = async (e: React.FormEvent) => {
 e.preventDefault();
 setLoading(true);
 setError("");

 try {
 // ***** // No check before creating account
 const userCredential = await createUserWithEmailAndPassword(
 auth,
 email,
 password
 );

 await createUserProfile(userCredential.user);

 router.push("/dashboard");
 } catch (error: any) {
 if (error.code === "auth/email-already-in-use") {
 setError("Email already registered.");
 }
 } finally {
 setLoading(false);
 }
};

Why This Approach Failed

The problem with this common approach:

  1. No pre-check: We only found out the email was taken AFTER trying to create the Firebase Auth account

  2. UID as document ID: We used user.uid as the Firestore document ID, but we don't know the UID until AFTER the user is created

  3. Can’t check by email: With UID as the document ID, we couldn’t easily check if an email already exists in Firestore

  4. fetchSignInMethodsForEmail() doesn't work: This Firebase method is often disabled due to "Email Enumeration Protection" for security reasons

The Solution: Use Email as Document ID

The key insight: Use the email address as the Firestore document ID instead of the UID. This way, we can check if an email exists BEFORE attempting to create the Firebase Auth account.

const createUserProfile = async (user: any) => {
 try {
 //****// Use EMAIL as document ID instead of UID
 const userDocRef = doc(db, "users", user.email);
 const userDoc = await getDoc(userDocRef);

 if (!userDoc.exists()) {
 const userProfile = {
 uid: user.uid, 
 email: user.email,
 userType: "staff",
 createdAt: serverTimestamp(),
 updatedAt: serverTimestamp(),
 };

 await setDoc(userDocRef, userProfile);
 } else {
 console.log("User profile already exists");
 }
 } catch (error) {
 console.error("Error creating user profile:", error);
 throw error;
 }
 };

 const handleSignUp = async (e: React.FormEvent) => {
 e.preventDefault();
 setLoading(true);
 setError("");

 try {

 //****// Check if email exists in Firestore BEFORE creating auth account
 const userDocRef = doc(db, "users", email);
 const userDoc = await getDoc(userDocRef);

 if (userDoc.exists()) {
 setError("You already have an account with this email. Please sign in instead.");
 setLoading(false);
 return; 
 }

 //****// Create Firebase Auth account
 const userCredential = await createUserWithEmailAndPassword(
 auth,
 email,
 password
 );

 //****// Create Firestore profile
 await createUserProfile(userCredential.user);
 router.push("/dashboard");
 
 } catch (error: any) {
 if (error.code === "auth/email-already-in-use") {
 setError("You already have an account with this email. Please sign in instead.");
 } else if (error.code === "auth/weak-password") {
 setError("Password should be at least 6 characters.");
 } else if (error.code === "auth/invalid-email") {
 setError("Invalid email address.");
 } else {
 setError(error.message);
 }
 } finally {
 setLoading(false);
 }
 };

Key Changes Explained

1. Changed Document ID from UID to Email

Before:

//***// Can't check email existence before signup
const userDocRef = doc(db, "users", user.uid);

After:

//***// Can check email existence anytime
const userDocRef = doc(db, "users", user.email);

2. Added Pre-Check Before Creating Account

Before:

//***// No check - try to create and handle error later
const userCredential = await createUserWithEmailAndPassword(auth, email, password);

After:

//***// Check Firestore first
const userDocRef = doc(db, "users", email);
const userDoc = await getDoc(userDocRef);

if (userDoc.exists()) {
 setError("You already have an account with this email. Please sign in instead.");
 return; 
}

//***// Only create account if email doesn't exist
const userCredential = await createUserWithEmailAndPassword(auth, email, password);

3. Store UID as a Field

const userProfile = {
 uid: user.uid, //****// Still keep UID for reference
 email: user.email,
 userType: "staff",
 createdAt: serverTimestamp(),
 updatedAt: serverTimestamp(),
};

Why This Solution Works

✅ Benefits:

  • Fast email lookup: Direct document access instead of querying

// Super fast O(1) lookup
const userDoc = await getDoc(doc(db, "users", email));
  • Prevents duplicates: Checks BEFORE attempting Firebase Auth signup

  • Better UX: Users get immediate feedback without waiting for Firebase Auth error

  • No enumeration protection issues: Doesn’t rely on fetchSignInMethodsForEmail()

  • Easy queries: Finding users by email is now trivial

//****// Get user by email
 const user = await getDoc(doc(db, "users", "[email protected]"));
```

**Still have UID**: Stored as a field for any UID-based operations

## Firestore Structure

Your Firestore `users` collection now looks like this:
```
users/
 ├── [email protected]/
 │ ├── uid: "abc123xyz"
 │ ├── email: "[email protected]"
 │ ├── userType: "staff"
 │ ├── createdAt: timestamp
 │ └── updatedAt: timestamp
 │
 └── [email protected]/
 ├── uid: "def456uvw"
 ├── email: "[email protected]"
 └── ...

What About Existing Users?

If you already have users with UID as document ID, you have a few options:

//***// One-time migration script
const migrateUsers = async () => {
 const usersRef = collection(db, "users");
 const snapshot = await getDocs(usersRef);
 
 for (const docSnap of snapshot.docs) {
 const userData = docSnap.data();
 
 //***// Create new document with email as ID
 await setDoc(doc(db, "users", userData.email), userData);
 
 //***// Optional: Delete old document
 //***// await deleteDoc(doc(db, "users", docSnap.id));
 }
};

NOTE: If you’re in development, delete old users and use the new structure

Here’s the Whole Sign-up Code:

"use client";
import React, { useState } from "react";
import { createUserWithEmailAndPassword } from "firebase/auth";
import { doc, setDoc, getDoc, serverTimestamp } from "firebase/firestore";
import { auth, db } from "@/firebase/client";
import { useRouter } from "next/navigation";
import { Input } from "../ui/input";
import Link from "next/link";

export default function SignUpForm() {
 const [email, setEmail] = useState("");
 const [password, setPassword] = useState("");
 const [error, setError] = useState("");
 const [loading, setLoading] = useState(false);
 const router = useRouter();

 const createUserProfile = async (user: any) => {
 try {
 //***// Use EMAIL as document ID instead of UID
 const userDocRef = doc(db, "users", user.email);
 const userDoc = await getDoc(userDocRef);

 if (!userDoc.exists()) {
 const userProfile = {
 uid: user.uid, // Store UID as a field for reference
 email: user.email,
 userType: "staff",
 createdAt: serverTimestamp(),
 updatedAt: serverTimestamp(),
 };

 await setDoc(userDocRef, userProfile);
 console.log("User profile created successfully");
 } else {
 console.log("User profile already exists");
 }
 } catch (error) {
 console.error("Error creating user profile:", error);
 throw error;
 }
 };

 const handleSignUp = async (e: React.FormEvent) => {
 e.preventDefault();
 setLoading(true);
 setError("");

 try {

 //***// Check if email exists in Firestore BEFORE creating auth account
 const userDocRef = doc(db, "users", email);
 const userDoc = await getDoc(userDocRef);

 if (userDoc.exists()) {
 setError("You already have an account with this email. Please sign in instead.");
 setLoading(false);
 return; 
 }

 //***// Create Firebase Auth account
 const userCredential = await createUserWithEmailAndPassword(
 auth,
 email,
 password
 );

 //***// Create Firestore profile
 await createUserProfile(userCredential.user);

 router.push("/dashboard");
 
 } catch (error: any) {
 if (error.code === "auth/email-already-in-use") {
 setError("You already have an account with this email. Please sign in instead.");
 } else if (error.code === "auth/weak-password") {
 setError("Password should be at least 6 characters.");
 } else if (error.code === "auth/invalid-email") {
 setError("Invalid email address.");
 } else {
 setError(error.message);
 }
 } finally {
 setLoading(false);
 }
 };

 return (
 <div className="min-h-screen flex items-center justify-center bg-neutral-900 py-12 px-4">
 <div className="max-w-md w-full space-y-8">
 <div>
 <h2 className="mt-6 text-center text-3xl font-medium capitalize text-gray-100">
 Create your account
 </h2>
 <p className="mt-2 text-center text-4xl text-gray-400">
 Welcome to Inspire
 </p>
 </div>

 <div className="mt-8 space-y-6">
 {error && (
 <div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
 {error}
 </div>
 )}

 <div className="space-y-4">
 <div>
 <label
 htmlFor="email"
 className="block text-sm font-medium text-gray-400"
 >
 Email address
 </label>
 <Input
 id="email"
 name="email"
 type="email"
 required
 value={email}
 onChange={(e) => setEmail(e.target.value)}
 className="mt-1 appearance-none relative block w-full h-12 border"
 placeholder="Email address"
 />
 </div>

 <div>
 <label
 htmlFor="password"
 className="block text-sm font-medium text-gray-400"
 >
 Password
 </label>
 <Input
 id="password"
 name="password"
 type="password"
 required
 value={password}
 onChange={(e) => setPassword(e.target.value)}
 className="mt-1 appearance-none relative block w-full h-12 border"
 placeholder="Password (min 6 characters)"
 />
 </div>
 </div>

 <div>
 <button
 type="button"
 onClick={handleSignUp}
 disabled={loading}
 className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50"
 >
 {loading ? "Creating account..." : "Sign Up"}
 </button>
 </div>

 <div className="text-center">
 <Link
 href="/signin"
 className="text-indigo-600 hover:text-indigo-500 text-sm"
 >
 Already have an account? Sign in
 </Link>
 </div>
 </div>
 </div>
 </div>
 );
}

Join Naymur on Peerlist!

Join amazing folks like Naymur and thousands of other builders on Peerlist.

peerlist.io/

It’s available... this username is available! 😃

Claim your username before it's too late!

This username is already taken, you’re a little late.😐

2

4

0