Firebase auth delete user: deleteUser and a recent sign-in (free)
lib/delete-account.ts
import { FirebaseError } from "firebase/app";
import {
AuthErrorCodes,
EmailAuthProvider,
GoogleAuthProvider,
deleteUser,
reauthenticateWithCredential,
reauthenticateWithPopup,
} from "firebase/auth";
import { auth } from "@/lib/firebase";
export type DeleteResult =
| { ok: true }
| { ok: false; reason: "signed-out" | "recent-login" | "error"; message: string };
const signedOut: DeleteResult = { ok: false, reason: "signed-out", message: "You're signed out. Sign in and try again." };
// Step 1: try the delete. Firebase refuses when the last sign-in isn't recent.
export async function deleteAccount(): Promise<DeleteResult> {
const user = auth.currentUser;
if (!user) return signedOut;
try {
await deleteUser(user);
return { ok: true };
} catch (e) {
// AuthErrorCodes.CREDENTIAL_TOO_OLD_LOGIN_AGAIN is "auth/requires-recent-login"
if (e instanceof FirebaseError && e.code === AuthErrorCodes.CREDENTIAL_TOO_OLD_LOGIN_AGAIN) {
return { ok: false, reason: "recent-login", message: "Confirm it's you, then we delete the account." };
}
return { ok: false, reason: "error", message: e instanceof Error ? e.message : "Something went wrong." };
}
}
export function usesPassword(): boolean {
return !!auth.currentUser?.providerData.some((p) => p.providerId === "password");
}
// Step 2: call this from a click, never a timer (browsers block popups without one).
// It signs the user in again, then runs the same delete.
export async function confirmAndDelete(password?: string): Promise<DeleteResult> {
const user = auth.currentUser;
if (!user) return signedOut;
try {
if (usesPassword()) {
if (!user.email || !password) {
return { ok: false, reason: "recent-login", message: "Enter your password to confirm." };
}
await reauthenticateWithCredential(user, EmailAuthProvider.credential(user.email, password));
} else {
await reauthenticateWithPopup(user, new GoogleAuthProvider()); // your provider here
}
await deleteUser(user);
return { ok: true };
} catch (e) {
return { ok: false, reason: "error", message: e instanceof Error ? e.message : "Something went wrong." };
}
}
lib/firebase.ts
import { getApp, getApps, initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
};
// Reuse the app on hot reload instead of initializing it twice.
export const app = getApps().length ? getApp() : initializeApp(firebaseConfig);
export const auth = getAuth(app);