如何修复 FirebaseError: 在 React Hook 中缺少或权限不足?

huangapple go评论71阅读模式
英文:

How to fix FirebaseError: Missing or insufficient permissions in React Hook?

问题

你正在使用React Hook、Next.js和Firebase Auth来在Firestore中创建用户的coin记录。这意味着一旦新用户注册,就应该自动在Firestore中为他们创建一个带有uidcoinscreatedAt字段的新文档。

但是,当我第一次登录时,遇到了一个错误消息,说:

hydration-error-info.js:27 Error in createUserCoinRecord: FirebaseError: Missing or insufficient permissions.

然而,如果我注销然后再登录,就不再出现这个错误了。

在检查Firebase数据库时,我注意到用户的uid在第一次登录后没有被创建,只有在第二次登录后才被创建。

以下是你提供的React Hook的代码:

export function useCoinRecord() {
  // ...
  return { created };
}

这是你的Firestore规则:

rules_version = '2';
service cloud.firestore {
  // ...
}

登录按钮:

import { signIn } from "next-auth/react";
//...
<button onClick={() => signIn("google")}>
  Sign In
</button>

使用Next Auth登录:

import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";

export const authOptions = {
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_ID,
      clientSecret: process.env.GOOGLE_SECRET,
    }),
  ],
};

export default NextAuth(authOptions);

然后使用useEffect来与Firebase登录:

import { useSession } from "next-auth/react";
import { auth, db } from "../firebase/firebase";
//...

const { created } = useCoinRecord();
//...

/api/firebase/create-custom-token

import { adminAuth } from "@/firebase/firebaseAdmin";
//...

firebase.js

import { getApp, getApps, initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
//...
export { db, auth };

firebaseAdmin.js

import admin from "firebase-admin";
//...
export { adminDb, adminAuth };

你的数据库结构如下:

/users/email@example.com/coins

其中email@example.comuid

造成FirebaseError的原因可能是Firestore规则不允许第一次登录时写入用户的uid,但在第二次登录后允许。你可以尝试在规则中添加一些日志来调试此问题,以查看请求的详细信息。另外,确保你的Firebase用户在第一次登录时正确地分配了uid。根据你的代码,uid似乎是从用户的电子邮件地址中派生的,这可能会导致问题。你可以在登录时确保分配一个唯一的uid

如果问题仍然存在,请提供有关Firebase规则、用户身份验证设置和数据库结构的更多详细信息,以便我能够提供更具体的帮助。

英文:

I am using a combination of React Hook, Next.js, and Firebase Auth to create a user coin record in Firestore. This means that as soon as a new user signs up, a new document with a uid should be automatically created for them in Firestore, along with the coins and createdAt fields.

However, when I sign in for the first time, I encounter an error message that says:
> hydration-error-info.js:27 Error in createUserCoinRecord: FirebaseError: Missing or insufficient permissions."

However, if I log out and sign in again, the error does not occur anymore.

Upon checking the Firebase database, I noticed that the uid for the user is not created after the first sign-in but is only created after the second sign-in.

Here's the code for the React Hook:

export function useCoinRecord() {
  const { data: session } = useSession();
  const userEmailRef = useRef(null);
  const [created, setCreated] = useState(false);

  async function createUserCoinRecord(uid) {
    await setDoc(doc(db, &quot;users&quot;, uid), {
      coins: 100000,
      createdAt: serverTimestamp(),
    });
  }

  useEffect(() =&gt; {
    async function createCoinRecordIfNeeded() {
      if (session) {
        if (userEmailRef.current !== session.user.email) {
          userEmailRef.current = session.user.email;
          try {
            await createUserCoinRecord(session.user.email);
            setCreated(true);
          } catch (error) {
            console.error(&quot;Error in createUserCoinRecord:&quot;, error);
          }
        }
      } else {
        userEmailRef.current = null;
      }
    }

    createCoinRecordIfNeeded();
  }, [session]);

  return { created };
}

And these are my Firestore rules:

rules_version = &#39;2&#39;;
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{uid} {
      allow read, write: if request.auth != null &amp;&amp; request.auth.uid == uid;
    }
    match /users/{uid}/{document=**} {
      allow read, write: if request.auth != null &amp;&amp; request.auth.uid == uid;
    }
  }
}

The Sign in button:

import { signIn } from &quot;next-auth/react&quot;;
//...
      &lt;button onClick={() =&gt; signIn(&quot;google&quot;)}&gt;
        Sign In
      &lt;/button&gt;

Sign in with Next Auth:

import NextAuth from &quot;next-auth&quot;;
import GoogleProvider from &quot;next-auth/providers/google&quot;;

export const authOptions = {
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_ID,
      clientSecret: process.env.GOOGLE_SECRET,
    }),
  ],
};

export default NextAuth(authOptions);

Then use useEffect to sign in with Firebase:

import { useSession } from &quot;next-auth/react&quot;;
import { auth, db } from &quot;../firebase/firebase&quot;;
//...

  const { data: session } = useSession();
  const { coinBalance, setCoinBalance } = useCoinBalanceContext();
  const [readyToFetchBalance, setReadyToFetchBalance] = useState(false);

  useEffect(() =&gt; {
    if (session) {
      signInWithFirebase(session).then((uid) =&gt; {
        if (uid) {
          setReadyToFetchBalance(true);
        }
      });
    }
  }, [session]);

  const signInWithFirebase = async (session) =&gt; {
    const response = await fetch(&quot;/api/firebase/create-custom-token&quot;, {
      method: &quot;POST&quot;,
      headers: {
        &quot;Content-Type&quot;: &quot;application/json&quot;,
      },
      body: JSON.stringify({ user: session.user }),
    });

    const customToken = await response.json();

    return signInWithCustomToken(auth, customToken.token)
      .then((userCredential) =&gt; {
        return userCredential.user.uid;
      })
      .catch((error) =&gt; {
        console.error(&quot;Firebase sign-in error:&quot;, error);
      });
  };

  const { created } = useCoinRecord();

  useEffect(() =&gt; {
    if (readyToFetchBalance &amp;&amp; created &amp;&amp; session?.user?.email) {
      (async () =&gt; {
        const balance = await getCoinBalance(session.user.email);
        balance &amp;&amp; setCoinBalance(balance);
      })();
    }
  }, [session, readyToFetchBalance, created]);

/api/firebase/create-custom-token

import { adminAuth } from &quot;@/firebase/firebaseAdmin&quot;;

export default async function handler(req, res) {
  if (req.method !== &quot;POST&quot;) {
    res.status(405).json({ message: &quot;Method not allowed&quot; });
    return;
  }

  const { user } = req.body;

  const uid = user.email;

  try {
    const customToken = await adminAuth.createCustomToken(uid);

    res.status(200).json({ token: customToken });
  } catch (error) {
    console.error(&quot;Error creating custom token:&quot;, error);
    res.status(500).json({ message: &quot;Error creating custom token&quot; });
  }
}

firebase.js

import { getApp, getApps, initializeApp } from &quot;firebase/app&quot;;
import { getAuth } from &quot;firebase/auth&quot;;
import { getFirestore } from &quot;firebase/firestore&quot;;

const firebaseConfig = {
apiKey: process.env.FIREBASE_APIKEY,
authDomain: process.env.FIREBASE_AUTHDOMAIN,
databaseURL: process.env.FIREBASE_DATABASEURL,
projectId: process.env.PROJECTID,
storageBucket: process.env.FIREBASE_STORAGEBUCKET,
messagingSenderId: process.env.FIREBASE_MESSAGINGSENDERID,
appId: process.env.FIREBASE_APPID,
measurementId: process.env.FIREBASE_MEASUREMENTID,
};

const app = getApps().length ? getApp() : initializeApp(firebaseConfig);
const db = getFirestore(app);
const auth = getAuth(app);
export { db, auth };

firebaseAdmin.js

import admin from &quot;firebase-admin&quot;;

const serviceAccount = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT_KEY);
if (!admin.apps.length) {
  admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
  });
}

const adminDb = admin.firestore();
const adminAuth = admin.auth();

export { adminDb, adminAuth };

My database is structured like this:

/users/email@example.com/coins

where email@example.com is the uid.

What could be causing the FirebaseError and how can I fix it please?

答案1

得分: 1

我意识到有两个同时运行的钩子之间存在竞争。

问题在于useCoinRecord()试图在用户进行身份验证之前创建一个新文档。为了解决这个问题,我移除了useCoinRecord()并修改了useEffect()钩子,以包括在设置ReadyToFetchBalance标志为true之前创建用户币记录所需的逻辑。

更新后的代码如下:

useEffect(() => {
  if (session) {
    signInWithFirebase(session).then(async (uid) => {
      if (uid) {
        await createUserCoinRecord(uid);
        setReadyToFetchBalance(true);
      }
    });
  }
}, [session]);

这应该解决竞争条件问题,并确保文档仅在用户进行身份验证后才创建。

英文:

I realized that there is a race between two hooks that are running at the same time.

The issue is that useCoinRecord() tries to create a new document before the user is authenticated. To fix this, I removed useCoinRecord() and modified the useEffect() hook to include the necessary logic for creating the user coin record before setting the ReadyToFetchBalance flag to true.

The updated code looks like this:

useEffect(() =&gt; {
  if (session) {
    signInWithFirebase(session).then(async (uid) =&gt; {
      if (uid) {
        await createUserCoinRecord(uid);
        setReadyToFetchBalance(true);
      }
    });
  }
}, [session]);

This should solve the race condition problem and ensure that the document is only created after the user is authenticated.

huangapple
  • 本文由 发表于 2023年4月19日 17:38:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/76052986.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定