0
votes

I'm new to Firebase and React Native. I am getting a Missing or Insufficient Privileges error when I add email authentication to the app and update the Authentication Rules.

I receive the error when trying to write to Firestore in the componentDidMount function in Screen1.

I have added the following rules for my app.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
       allow read, write: if request.auth != null;
    }
  }
}

If I change to allow read, write: if true; everything works fine. However, that is not what I need for a production app that I plan on deploying.

I checked my app by pulling in the current User and it looks like the user is logged in and authenticated.

And I get the authentication error: [FirebaseError: Missing or insufficient permissions.] when I try to do the following:

Config.js

import Firebase from "firebase";

// Web App Firebase Configuration
var firebaseConfig = {
    apiKey: "pretendapikeytoprotect",
    authDomain: "pretendapp.firebaseapp.com",
    databaseURL: "https://pretendapp.firebaseio.com",
    projectId: "pretendapp",
    storageBucket: "pretendapp.appspot.com",
    messagingSenderId: "messengerSenderIDnumbers",
    appId: "appIDNumber",
    measurementId: "measureIDnumberiD"
  };

  const app = Firebase.initializeApp(firebaseConfig);

  // Initialize Cloud Firestore through firebase
  export const db = app.firestore();
  db.settings({experimentalForceLongPolling: true});

App.js

/**
 * @format
 * @flow strict-local
 */
import 'react-native-gesture-handler';
import React, {Component} from 'react';
import Providers from './src/navigation';

import {
  Alert,
  I18nManager,
  YellowBox
} from 'react-native';

//Ignore Warning about Setting a Timer/No known fix at this time.
YellowBox.ignoreWarnings(['Setting a timer']);

type Props = {};

export default class App extends React.Component<Props, State> {

    constructor(props: Props) {
      super(props);
      
    }

    componentWillUnmount() {
      //willMountFunctions
    }

    componentDidMount() {
    //DidMountFunctions    
    }

  render() {
    return (
      <Providers />
    )
  }
}

Providers - /src/navigation/index.js

import React from 'react';
import { AuthProvider } from './AuthProvider';
import Routes from './Routes';

export default function Providers()  {
    return (
    <AuthProvider>
      <Routes />
    </AuthProvider>
  );
}

Routes.js

import React, { useContext, useState, useEffect } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import auth from '@react-native-firebase/auth';
import AuthStack from './AuthStack';
import HomeStack from './HomeStack';
import { AuthContext } from './AuthProvider';
import Loading from '../components/Loading';

export default function Routes() {

    const { user, setUser } = useContext(AuthContext);
    const [loading, setLoading] = useState(true);
    const [initializing, setInitializing] = useState(true);
    
    // Handle user state changes
    function onAuthStateChanged(user) {
      setUser(user);
      if (initializing) setInitializing(false);
      setLoading(false);

      if (user) {
        console.log('user is logged');
      }
    }

    useEffect(() => {
      const subscriber = auth().onAuthStateChanged(onAuthStateChanged);
      return subscriber; // unsubscribe on unmount
    }, []);

    if (loading) {
      return <Loading />;
    }

    return (
      <NavigationContainer>
        {user ? <HomeStack /> : <AuthStack />}
      </NavigationContainer>
    );
  }

HomeStack.js

import React  from 'react';
import { createStackNavigator } from '@react-navigation/stack';

//Import Components
import HomeScreen from '../screens/HomeScreen';
import Screen1 from '../screens/Screen1';

const Stack = createStackNavigator();

export default function HomeStack() {

  return (
    <Stack.Navigator initialRouteName="Home">
        <Stack.Screen name="Home"
                  component = {HomeScreen}
                  options={{ title: 'Home Screen' }} />
        <Stack.Screen name="Screen1" 
                  component={Screen1}
                  options={{ title: 'Screen 1' }}  />
    </Stack.Navigator>
  );
}

Helper file (helpers.js)

const RNFS = require('react-native-fs');
import storage from '@react-native-firebase/storage';
import firestore from '@react-native-firebase/firestore';
import { db } from '../config';
import { Platform } from 'react-native';
import { currentUser } from '../navigation/AuthProvider';
import auth from '@react-native-firebase/auth';

// removed other functions not relevant to question //

 export const WriteSessionData = async (sessionID) => {
    
      console.log("Write Session current User Logged in ", currentUser.uid);
      console.log("Current Session User in Write Session ", auth().currentUser)

      const session = db.collection('sessions').doc(sessionID);
      
      await session.set({
        name: '',
        email: '',
        timedate : Date.now(),})
        .then(() => {
          console.log("Session Data Added to Database");
        }).catch((err) => {
      console.log("Error in Writing to Database ", "User Logged in as: ", currentUser.uid, err);
    });
}
  }

AuthStack.js

import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import SignupScreen from '../screens/SignupScreen';
import LoginScreen from '../screens/LoginScreen';
const Stack = createStackNavigator();
export default function AuthStack() {
  return (
    <Stack.Navigator initialRouteName='Login'>
      <Stack.Screen
        name='Login'
        component={LoginScreen}
        options={{ header: () => null }}
      />
      <Stack.Screen name='Signup' component={SignupScreen} />
    </Stack.Navigator>
  );
}

Login Screen

import React, { useState, useContext } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, Image } from 'react-native';
import FormButton from '../components/FormButton';
import FormInput from '../components/FormInput';

import { AuthContext } from '../navigation/AuthProvider';

export default function LoginScreen({ navigation }) {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const { login } = useContext(AuthContext);
  return (

    <View style={styles.container}>
      <FormInput
        value={email}
        placeholderText='Email'
        onChangeText={userEmail => setEmail(userEmail)}
        autoCapitalize='none'
        keyboardType='email-address'
        autoCorrect={false}
      />
      <FormInput
        value={password}
        placeholderText='Password'
        onChangeText={userPassword => setPassword(userPassword)}
        secureTextEntry={true}
      />
      <FormButton buttonTitle='Login' onPress={() => login(email, password)} />
      <TouchableOpacity
        style={styles.navButton}
        onPress={() => navigation.navigate('Signup')}
      >
        <Text style={styles.navButtonText}>New user? Join here</Text>
      </TouchableOpacity>
    </View>

  );
}

AuthProvider.js

import React, { createContext, useState } from 'react';
import auth from '@react-native-firebase/auth';

export const AuthContext = createContext({});

export const AuthProvider = ({ children }) => {
    const [user, setUser] = useState(null);

    return (
      <AuthContext.Provider
        value={{
          user,
          setUser,
          login: async (email, password) => {
            try {
              await auth().signInWithEmailAndPassword(email, password);
            } catch (e) {
              console.log(e);
            }
          },
          register: async (email, password) => {
            try {
              await auth().createUserWithEmailAndPassword(email, password);
            } catch (e) {
              console.log(e);
            }
          },
          logout: async () => {
            try {
              await auth().signOut();
            } catch (e) {
              console.error(e);
            }
          }
        }}
      >
        {children}
      </AuthContext.Provider>
    );
  };

export const currentUser = auth().currentUser;

HomeScreen.js

import React, { Component, useContext, useState} from 'react';
import { Alert, Button, View, Text, Image, PermissionsAndroid, TouchableHighlight, TextInput } from 'react-native';
import { styles, buttons } from './styles' 
import auth from '@react-native-firebase/auth';
import FormButton from '../components/FormButton';
import { AuthContext } from '../navigation/AuthProvider';

export default function HomeScreen(props) {

  const { route, navigation } = props

  const { user, logout } = useContext(AuthContext);

  function  navigate() {
    console.log("User Logged In", auth().currentUser.uid);
    console.log("Navigating to Screen 1 ....");
    navigation.navigate('Screen1');
  }

  return (
        <View>
          <View style={styles.container}>
          <Text style={styles.description}>You are logged in as {user.email}</Text>
          <FormButton buttonTitle="Get Started" onPress={()=> navigate()} />
          <FormButton buttonTitle='Logout' onPress={() => logout()} />
         </View>
         </View>
      );
}

Screen1.js

import React, { Component } from 'react';
import { Alert, Button, View, Text, Image, PermissionsAndroid, TouchableHighlight } from 'react-native';
import { styles } from './styles' 
import { db } from '../config';
import { WriteSessionData} from '../utils/helpers';
import storage from '@react-native-firebase/storage';
import { currentUser } from '../navigation/AuthProvider';
import FormButton from '../components/FormButton';

class Screen1 extends Component {

  _isMounted = false;
  
    constructor(props) {
        super(props);
       
        this.state = {
          sessionID: '12345' //random id that will be passed down
        };        
      }

      componentWillUnmount() {
        clearInterval(this._progressInterval);
        this._isMounted = false;
      }
  
      componentDidMount() { 

        this._isMounted = true;


        console.log("User ID in Screen 1 componentDidMount", currentUser.uid);
        WriteSessionData(this.state.sessionID);

        if(this._isMounted){
          //do some other commands
        }        
      }

render() {
      return (
        <View>
          <Text style={styles.title}>Screen 1</Text>
          <Text style={styles.description}>User Email: {currentUser.email} 
          </Text>
         </View> 
      );
    }
  }
  
  export default Screen1;

I can even return the user's UID in console.log. I can also see the user is logged in through the Firebase Web Console.

Am I missing adding some privileges in IAM? Is there some response header issue with the React Native libraries or something I should resolve.

I have the following libraries and versions installed:

    "@react-native-firebase/app": "^8.2.0",
    "@react-native-firebase/auth": "^8.2.0",
    "@react-native-firebase/firestore": "^7.4.3",
    "@react-native-firebase/storage": "^7.2.2",
    "@react-navigation/native": "^5.6.1",
    "@react-navigation/stack": "^5.6.2",
    "firebase": "^7.16.0",
    "react": "16.11.0",
    "react-native": "0.62.2",
1
If the permissions deny the write, then auth is null at the time of the write. So you have a timing issue. Your auth is likely not being processed before you try to access the database. Because you have included exerpts instead of a runnable code sample, this isn't actionable and I can't offer more than guesses. See how to ask and creating an mcve.Kato
Can auth be null if I can return the UID in the console.log of the catch? console.log("Error in Writing to Database ", "User Logged in as: ", currentUser.uid, err);Nikiya Simpson
The easiest way to troubleshoot is typically to log the UID right before you access the database. So right before your await session.set({... add a console.log(firebase.auth().currentUser) and check its output. If you're having trouble getting it to work after this troubleshooting, please edit your question to include the updated code and its output, so that we can check.Frank van Puffelen
I have added some additional code. I was able to add log to UID right before the session.set and the app was able to log the UID. I'm still getting the error though.Nikiya Simpson

1 Answers

1
votes

I wonder if your Config.js file is confusing the SDK. Per the react-native-firebase docs:

Unlike the Firebase Web SDK, there is no need to manually call the initializeApp method with your project credentials. The native Android & iOS SDKs automatically connect to your Firebase project using the credentials provided during the Getting Started installation steps.

Additionally, you might want to consider either:

  • Getting rid of AuthProvider.js and just calling the @react-native-firebase/auth library directly when you need to check auth state or user information

or:

  • Moving your onAuthStateChanged listener into AuthProvider.js and making sure you use the AuthProvider instead of calling @react-native-firebase/auth directly.

Choosing one of those options instead of a little bit of both will help you keep track of what is going on with your auth state.