如何修复React Hook中FirebaseError:权限缺失或不足问题
问题分析与修复
问题描述
结合React Hook、Next.js和Firebase Auth在Firestore中创建用户金币记录,预期新用户注册后自动创建包含uid、coins和createdAt字段的文档。但首次登录时出现权限错误:
hydration-error-info.js:27 Error in createUserCoinRecord: FirebaseError: Missing or insufficient permissions
退出后再次登录无此错误,且首次登录后Firestore未创建对应文档,二次登录才会创建。
错误原因
- 执行顺序不匹配:
useCoinRecord中的useEffect监听NextAuth的session变化,一旦session存在就立即调用createUserCoinRecord。但此时Firebase Auth的自定义令牌登录流程(signInWithFirebase)还在异步执行中,Firestore请求时Firebase Auth实例处于未认证状态,导致request.auth为null,触发Firestore规则的权限校验失败。 - 二次登录缓存机制:第二次登录时,Firebase Auth会使用本地缓存的用户信息快速完成认证,此时
session出现时Firebase已经处于登录状态,Firestore请求能通过权限校验,成功创建文档。
修复方案
方案1:修改useCoinRecord,监听Firebase Auth用户状态
将useCoinRecord的触发逻辑从依赖NextAuth session改为监听Firebase Auth的用户登录状态,确保只有在Firebase认证完成后才执行创建文档的操作:
import { useState, useEffect } from "react"; import { getAuth, onAuthStateChanged } from "firebase/auth"; import { doc, setDoc, getDoc, serverTimestamp } from "firebase/firestore"; import { db } from "../firebase/firebase"; export function useCoinRecord() { const [created, setCreated] = useState(false); const auth = getAuth(); async function createUserCoinRecord(uid) { const userDocRef = doc(db, "users", uid); const userDocSnap = await getDoc(userDocRef); // 仅当文档不存在时创建 if (!userDocSnap.exists()) { await setDoc(userDocRef, { coins: 100000, createdAt: serverTimestamp(), }); } } useEffect(() => { const unsubscribe = onAuthStateChanged(auth, async (user) => { if (user) { try { await createUserCoinRecord(user.uid); setCreated(true); } catch (error) { console.error("Error in createUserCoinRecord:", error); } } else { setCreated(false); } }); // 清理监听 return () => unsubscribe(); }, [auth]); return { created }; }
方案2:调整组件执行顺序,在Firebase登录成功后创建记录
如果希望保留原有的NextAuth session监听逻辑,可将创建文档的操作移到signInWithFirebase成功回调中,确保Firebase认证完成后再执行:
import { useSession } from "next-auth/react"; import { auth, db } from "../firebase/firebase"; import { doc, setDoc, getDoc, serverTimestamp } from "firebase/firestore"; // ... const { data: session } = useSession(); const { coinBalance, setCoinBalance } = useCoinBalanceContext(); const [readyToFetchBalance, setReadyToFetchBalance] = useState(false); const [created, setCreated] = useState(false); async function createUserCoinRecord(uid) { const userDocRef = doc(db, "users", uid); const userDocSnap = await getDoc(userDocRef); if (!userDocSnap.exists()) { await setDoc(userDocRef, { coins: 100000, createdAt: serverTimestamp(), }); } } useEffect(() => { if (session) { signInWithFirebase(session).then(async (uid) => { if (uid) { try { await createUserCoinRecord(uid); setCreated(true); setReadyToFetchBalance(true); } catch (error) { console.error("Error creating coin record:", error); } } }); } }, [session]); // ... 其余代码不变
额外优化:确保Firestore规则与UID逻辑一致
当前规则中request.auth.uid == uid与你使用用户邮箱作为UID的逻辑匹配,无需修改,可补充创建权限的明确声明(可选):
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /users/{uid} { allow read, write, create: if request.auth != null && request.auth.uid == uid; } match /users/{uid}/{document=**} { allow read, write: if request.auth != null && request.auth.uid == uid; } } }
内容的提问来源于stack exchange,提问作者Tui
相关产品推荐
相关产品推荐

