简体   繁体   中英

“Insufficient permissions” error when signing out of Firebase React app

My project is in React/Firebase. I get an "Uncaught Error in onSnapshot: FirebaseError: Missing or insufficient permissions." when logging out of it. There are read/write rules in place so it makes sense that I am getting the error.

I think it has to do with how the onSnapshot listeners are getting detached, as per: Firebase rules when user log out 'Missing or insufficient permissions. Error: Missing or insufficient permissions'

I'm not sure what I'm doing wrong in detaching them -- there are 6 snapshot listeners in the project, and (I think) I've narrowed it down to the following component provides User info:

const UserProvider = props => {
  const [{ user }, setUser] = useState({ user: null });
  const unsubscribeRef = useRef(null);

  useEffect(() => {
    auth.onAuthStateChanged(async user => {
      if (user) {
        const userRef = await createUserProfileDocument(user);

        unsubscribeRef.current = userRef.onSnapshot(snapshot => {
          setUser({ user: { uid: snapshot.id, ...snapshot.data() } });
        });
      }
    });

    return () => unsubscribeRef.current();
  }, []);

  return (
    <UserContext.Provider value={user}>{props.children}</UserContext.Provider>
  );
};

Before, I was just attaching the unsubscribe variable directly to the userRef.onSnapshot bit, with no luck -- which is why I tried useRef here. I wonder if I need to attach somehow the point when signOut is called? No other examples of React/Firebase apps seem to use that. Lastly, I don't understand why but the return statement inside the useEffect never seems to be reached here.

If it helps, this is the Authentication component:

const Authentication = () => {
  const history = useHistory();

  useEffect(() => {
    auth.onAuthStateChanged(async userAuth => {
      if (userAuth) {
        if (history.location.pathname === ROUTES.SIGN_IN)
          history.push(ROUTES.HOME);
      } else {
        history.push(ROUTES.SIGN_IN);
      }
    });
  }, [history]);

  return null;
};

Edit: These are the rules in Firestore:

match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if getUserData().admin == true;
    }

    function getUserData() {
      return get(/databases/$(database)/documents/users/$(request.auth.uid)).data
    }
  }

I'm not sure what else would be relevant here. Upon signing out, the user is routed back to the SignIn page which doesn't use anything from the database.

App() is structured like so:

function App() {
  return (
    <UserProvider>
      <Router>
        <Authentication />
        <Switch>
          <Route path="/signin" component={SignIn} />
          <Route path="/forgot" component={ForgotPasswordView} />

          <CatalogProvider>
            <Route path="/" component={Main} />
          </CatalogProvider>
        </Switch>
      </Router>
    </UserProvider>
  );
}

And Main contains the rest of the working parts of the app.

One thing to realize is that onAuthStateChanged will give you a null user object when the user signs out. Your code doesn't seem to handle that case.

Another thing to realize is that when the user signs out, if any active listener on a query would become invalid due to security rules (for example, request.auth becomes null), then the listener will be cut off with a permission error. You should remove all persistent listeners on queries that depend on authentication before signing out, or simply accept that the errors will happen and the listeners automatically removed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM