You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

解决Firebase NodeJS SDK注册登录报错auth/error-code:-47问题

解决Firebase Auth错误auth/error-code:-47的方案

问题背景

我开发了一个注册表单,允许用户输入邮箱、密码等凭证,目标是创建Firebase Auth账户,并在MongoDB中存储其他用户信息。流程设定为:用户提交表单后,先发送POST请求将信息保存到MongoDB,再调用getAuth()提供的createUserWithEmailAndPassword方法创建账户;同时在Firebase Functions中使用firebase-functions/v2/identity的beforeUserCreated和beforeUserSignedIn方法修改账户声明。

近期持续遇到错误,日志信息有限,且signInWithGoogle也出现相同浏览器错误:

FirebaseError: Firebase: Error (auth/error-code:-47)

核心代码

1. 邮箱注册处理函数emailSignUp

function emailSignUp(event) {
        setSignUpClicked(true);
        event.preventDefault();
        if (password !== confirmPassword) {
            console.log("Passwords do not match!");
            setSignUpClicked(false);
            return;
        }

        let formData = new FormData(document.getElementById("emailSignUp"));

        fetch(`${process.env.REACT_APP_DEPLOYED_API_URL}/auth/user/register`, {
            method: "POST",
            body: formData,
        })
            .then((response) => response.json())
            .then((data) => {
                if (data.status === "error") {
                    setStatus("failure");
                    setSignUpClicked(false);
                    throw new Error("Invalid form input. Please check again.");
                } else {
                    createUserWithEmailAndPassword(auth, email, password)
                        .then((userCredential) => {
                            setStatus("success");
                            setSignUpClicked(false);
                            setShowMyModal(true);
                        })
                        .catch((error) => {
                            const errorCode = error.code;
                            const errorMessage = error.message;
                            setStatus("failure");
                            setSignUpClicked(false);
                            console.log(error);
                        });
                    console.log("Success:", data);
                }
            })
            .catch((error) => {
                setStatus("failure");
                setSignUpClicked(false);
                console.error("Error:", error);
            });
    }

2. Firebase Functions的index.js

export const beforecreated = beforeUserCreated(async (event) => {
    try {
        const email = event.data.email;
        const checkUser = await User.findOne({ email: event.data.email });

        if (event.additionalUserInfo.providerId === "google.com") {
            const checkOrg = await Organization.findOne({ email });
            if (checkUser) {
                return {
                    displayName: checkUser.name,
                    customClaims: {
                        role: "user",
                        userId: checkUser._id,
                    },
                };
            } else if (checkOrg) {
                return {
                    displayName: checkUser.name,
                    customClaims: {
                        role: "org",
                        orgId: checkUser._id,
                    },
                };
            } else {
                const name = event.data.displayName;
                const email = event.data.email;
                const photoURL = event.data.photoURL;

                const response = await fetch(photoURL);
                const buffer = await response.arrayBuffer();
                const storagePath = `user/${email}/profile.jpeg`;
                const storageRef = ref(storage, storagePath);
                const metadata = {
                    contentType: "image/jpeg",
                };
                const snapshot = await uploadBytesResumable(
                    storageRef,
                    buffer,
                    metadata
                );
                if (snapshot.state === "success") {
                    const user = await User.create({
                        name,
                        email,
                        photoPathFirestore: storagePath,
                    });
                    console.log("Successfully created new user in DB!");
                    return {
                        displayName: user.name,
                        customClaims: {
                            role: "user",
                            userId: user._id,
                        },
                    };
                } else {
                    throw new Error("Error occurred while creating User");
                }
            }
        } else {
            if (checkUser) {
                return {
                    customClaims: {
                        role: "user",
                    },
                };
            }
            return {
                customClaims: {
                    role: "org",
                },
            };
        }
    } catch (err) {
        throw new Error(err);
    }
});

export const beforesignin = beforeUserSignedIn(async (event) => {
    try {
        const email = event.data.email;
        const checkUser = await User.findOne({ email });

        if (checkUser) {
            return {
                displayName: checkUser.name,
                customClaims: {
                    role: "user",
                    userId: checkUser._id,
                },
            };
        } else {
            const checkOrg = await Organization.findOne({ email });
            return {
                displayName: checkOrg.name,
                customClaims: {
                    role: "org",
                    orgId: checkOrg._id,
                },
            };
        }
    } catch (err) {
        console.log(err);
        throw new Error(err);
    }
});

3. MongoDB用户注册API接口/auth/user/register

router.post("/register", async (req, res) => {
    try {
        const { name, email, phone } = req.body;

        if (!name || !email) {
            throw new Error(
                "Following fields are mandatory: name, email, password, photo"
            );
        } else {
            if (!req.files[0]) {
                throw new Error(
                    "Following fields are mandatory: name, email, password, photo"
                );
            }
            const file = req.files[0];
            const filename = file.originalname;
            const extension = filename.split(".").pop();
            if (
                extension !== "png" &&
                extension !== "jpg" &&
                extension !== "jpeg"
            ) {
                response_400(
                    res,
                    "Invalid file format. Only .jpg, .png, .jpeg files are allowed"
                );
            } else {
                Jimp.read(file.buffer)
                    .then((image) => {
                        return image
                            .quality(100)
                            .getBufferAsync(Jimp.MIME_JPEG);
                    })
                    .then(async (jpegBuffer) => {
                        console.log("Image converted successfully!");

                        const pathToFile = `user/${email}/profile.jpeg`;
                        const storageRef = ref(storage, pathToFile);

                        const metadata = {
                            contentType: "image/jpeg",
                        };

                        const snapshot = await uploadBytesResumable(
                            storageRef,
                            jpegBuffer,
                            metadata
                        );
                        if (snapshot.state === "success") {
                            const user = await User.create({
                                name,
                                email,
                                photoPathFirestore: pathToFile,
                                phone,
                            });
                            console.log("Successfully created new user in DB!");
                            response_200(
                                res,
                                "Successfully created new user in DB"
                            );
                        } else {
                            response_500(
                                res,
                                "Error occurred while uploading user file"
                            );
                        }
                    })
                    .catch((err) => {
                        console.error("Error converting image:", err);
                        response_500(res, "Error converting image");
                    });
            }
        }
    } catch (err) {
        console.log(err);
        res.send(err);
    }
});

问题排查与解决步骤

1. 定位错误根源

auth/error-code:-47本质是Firebase Auth的beforeUserCreated或beforeUserSignedIn触发器执行失败导致的——这类触发器是同步执行的,一旦抛出未处理异常,就会阻断Auth流程并返回这个模糊错误码。

2. 修复触发器中的逻辑错误

(1) 修正beforecreated的引用错误

当检测到checkOrg存在时,代码错误地引用了checkUser的属性,改为:

// 原错误代码
return {
    displayName: checkUser.name,
    customClaims: {
        role: "org",
        orgId: checkUser._id,
    },
};
// 修正后
return {
    displayName: checkOrg.name,
    customClaims: {
        role: "org",
        orgId: checkOrg._id,
    },
};

(2) 修复beforesignin的空值风险

当checkUser和checkOrg都不存在时,访问checkOrg.name会抛出异常,添加空值判断:

if (checkUser) {
    return {
        displayName: checkUser.name,
        customClaims: {
            role: "user",
            userId: checkUser._id,
        },
    };
} else {
    const checkOrg = await Organization.findOne({ email });
    if (!checkOrg) {
        throw new Error(`No user or organization found for email: ${email}`);
    }
    return {
        displayName: checkOrg.name,
        customClaims: {
            role: "org",
            orgId: checkOrg._id,
        },
    };
}

3. 优化错误日志

在Firebase Functions的异常捕获中输出详细错误,便于调试:

catch (err) {
    console.error("Trigger execution failed:", err.stack);
    throw new Error(`Auth trigger error: ${err.message}`);
}

前端启用Auth调试日志:

import { getAuth, setLogLevel } from "firebase/auth";
const auth = getAuth();
setLogLevel('debug');

4. 调整注册流程的原子性

当前先写MongoDB再创建Auth账户的流程,若Auth创建失败会产生孤立数据。建议改为:

  1. 先调用createUserWithEmailAndPassword创建Auth账户
  2. 成功后再调用API写入MongoDB
    或使用MongoDB事务确保两者一致性。

5. 完善Google登录的图片处理逻辑

Google登录分支中直接下载photoURL可能遇到跨域或资源不可访问的情况,添加响应校验:

const response = await fetch(photoURL);
if (!response.ok) {
    throw new Error(`Failed to fetch profile photo: ${response.statusText}`);
}
const buffer = await response.arrayBuffer();

内容的提问来源于stack exchange,提问作者BlazeRod11

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 12:42:04