skip to Main Content

Below are my Firestore rules. I have a "users" collection where the document ID is the user’s UID in the authentication. I want only admins to access some users data or the user itself.

service cloud.firestore {
  match /databases/{database}/documents {
    match /{users=**} {
      allow read: if  isOwner() || isAdmin();
      allow write: if isOwner();
    }
    
    function isOwner() {
      return request.auth != null && request.auth.uid == resource.id;
    }
    
    function isAdmin() {
        return request.auth != null && request.auth.token.user_type == 3;
    }
  }
}

This is the function I use to fetch data:

const fetchUserDetails = async (userId: string) => {
    const userDocRef = doc(FIREBASE_DB, 'users', userId);
    console.log('Fetching userid: ', userId);
    const userDocSnapshot = await getDoc(userDocRef);

    if (userDocSnapshot.exists()) {
        const userData = userDocSnapshot.data();
        const addressesCollectionRef = collection(
            FIREBASE_DB,
            'users',
            userId,
            'addresses',
        );
        const addressesSnapshot = await getDocs(addressesCollectionRef);

        const addresses = addressesSnapshot.docs.map(addressDoc =>
            addressDoc.data(),
        );

        return {
            ...userData,
            addresses,
        };
    }
    // Handle the case where the user does not exist in Firestore
    console.error('User not found in Firestore');
    return null;
};

When I try the simulation in the Firestore everything works fine. Admin can get all users information and normal users only can get their information. But when I try to fetch user data with the above code in my React Native application, I get this error: FirebaseError: Missing or insufficient permissions. For the admin it works fine, it can fetch the data of itself. But regular user cant fetch its own data. I checked that regular user only trying to access its own data not others.

Also If I tried to change my security rules to this. Now even the admin cant fetch data.

rules_version = '2';

service cloud.firestore {
  match /databases/{database}/documents {
    
    match /users/{userId} {
      allow read: if  isOwner(userId) || isAdmin();
      allow write: if isOwner(userId);
    }
    
    function isOwner(userId) {
      return request.auth != null && request.auth.uid == userId;
    }
    
    function isAdmin() {
        return request.auth != null && request.auth.token.user_type == 3;
    }
  }
}

3

Answers


  1. Chosen as BEST ANSWER

    So, I had a collection called "adresses" inside every user document and I was trying to fetch these users' adresses as well. But I did not consider it in my security rules. This is the working version of my rules. Everything is working as expected.

    rules_version = '2';
    
    service cloud.firestore {
      match /databases/{database}/documents {
        match /users/{userId} {
          allow read, write: if isOwner(userId) || isAdmin();
    
          
        }
        
        // Match any document in the 'addresses' subcollection of a user
        match /users/{userId}/{addresses=**} {
            allow read, write: if isOwner(userId) || isAdmin();
        }
        
        function isOwner(userId) {
          return request.auth != null && request.auth.uid == userId;
        }
        
        
        function isAdmin() {
          return request.auth != null && request.auth.token.user_type == 3;
        }
      }
    }
    

  2. If you want isOwner() to work when you read data using the following lines of code:

    const userDocRef = doc(FIREBASE_DB, 'users', userId);
    const userDocSnapshot = await getDoc(userDocRef);
    

    Then you have to change the rules to:

    service cloud.firestore {
      match /databases/{database}/documents {
        //            👇
        match /users/{uid} {
          allow read: if  isOwner(uid) || isAdmin();
          allow write: if isOwner(uid);
        }
        
        function isOwner(uid) {
          return request.auth != null && request.auth.uid == uid;
        }
        
        function isAdmin() {
            return request.auth != null && request.auth.token.user_type == 3;
        }
      }
    }
    

    In this way, you will make sure your users will be able to only read and write their own data.

    Please note that you can make your security rules cascade to nested sub-collections. So if you want to be able to read the data under the addresses sub-collection using the following lines of code:

    const addressesCollectionRef = collection(
        FIREBASE_DB,
        'users',
        userId,
        'addresses',
    );
    const addressesSnapshot = await getDocs(addressesCollectionRef);
    

    Then you have to specify a recursive wildcard (=**) in the match clause for the document ID. Since you do that for the users collection, your current access rules don’t cascade. So you have to use something like:

    service cloud.firestore {
      match /databases/{database}/documents {
        //               👇
        match /users/{uid=**} {
          allow read: if  isOwner(uid) || isAdmin();
          allow write: if isOwner(uid);
        }
        
        function isOwner(uid) {
          return request.auth != null && request.auth.uid == uid;
        }
        
        function isAdmin() {
            return request.auth != null && request.auth.token.user_type == 3;
        }
      }
    }
    
    Login or Signup to reply.
  3. function isOwner() {
      return request.auth != null && request.auth.uid == resource.id;
    }
    

    Replace resource.id with resource.data.id if what you want to access is the id field of the fetched document.

    Note: resource.id and resource.data.id will always return null if the document does not exist.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search