skip to Main Content

In my database, I have a collection containing documents from various users. Each document has a user property with the user’s email as its value. (I’m going to change that to userID later on.)
I want to make sure that a user can only read, update and delete documents where the user’s email (request.auth.token.email) matches the value of the user property.

I spent several hours trying to find a solution. I think this is my best approach, but it doesn’t work:

service cloud.firestore {
  match /databases/{database}/documents {
  
    match /{document=**} {
      allow read, write: if isSignedIn() && emailVerified();
    }

    match /fooCollection/{document} {
        allow create: if isSignedIn() && emailVerified() && userValueIsNotManipulated();
        allow update, delete: if isSignedIn() && emailVerified() && isOwner() && userValueIsNotManipulated();
        allow read: if isSignedIn() && emailVerified() && isOwner();
    }
    
    /// Functions ///
    function isSignedIn() {
        return request.auth != null
    }

    function emailVerified() {
        return request.auth.token.email_verified
    }
    
    function isOwner() {
        return request.auth.token.email == resource.data.user
    }
    
    function userValueIsNotManipulated() {
        return request.resource.data.user == request.auth.token.email
    }
  }
}

I also tried:

match /fooCollection/{user}
match /fooCollection/{user} {
        ...
        ...
        allow read: if isSignedIn() && emailVerified() && request.auth.token.email == user;
    }
match /fooCollection/{user}/{document=**}
match /fooCollection/{document}/{user}

I have a second collection with a similar data structure. In the current state of my project, I make a request to firestore to get the fooCollection. Then, I filter it locally for the user’s documents. The request for the second collection works in the same way. I, therefore, expected my request for the first collection to fail with the above securety rules, and the request for the second collection to largely work. In my attempts so far, either both requests were successful, or both requests failed.

So, how can I make sure, that a user can only read, update and delete documents where the user’s email (request.auth.token.email) equals the value of the property user?

2

Answers


  1. Chosen as BEST ANSWER

    Thanks @Bjorn Ironside!

    Here is my solution:

    service cloud.firestore {
      match /databases/{database}/documents {
        allow read, write: if false;
    
        match /fooCollection/{document} {
            allow create: if isSignedIn() && emailVerified() && userValueIsNotManipulated();
            allow update, delete: if isSignedIn() && emailVerified() && isOwner() && userValueIsNotManipulated();
            allow read: if isSignedIn() && emailVerified() && isOwner();
        }
    
        match /secondColletion/{document} {
            allow read, write: if isSignedIn() && emailVerified();
        }
        
        /// Functions ///
        function isSignedIn() {
            return request.auth != null
        }
    
        function emailVerified() {
            return request.auth.token.email_verified
        }
        
        function isOwner() {
            return request.auth.token.email == resource.data.user
        }
        
        function userValueIsNotManipulated() {
            return request.resource.data.user == request.auth.token.email
        }
      }
    }
    

    Now it works as expected. When my app tries to access the fooCollection I get the FirebaseError: Missing or insufficient permissions.

    Only logged in and authenticated users can access the secondCollection. In the fooCollection you can only access your own documents. Apart from that, no access is possible.


  2. Security rules allow for overlapping rules.
    "It’s possible for a document to match more than one match statement. In the case where multiple allow expressions match a request, the access is allowed if any of the conditions is true"
    It looks like this statement will allow any read or write and that will override the second match statement.

    match /{document=**} {
      allow read, write: if isSignedIn() && emailVerified();
    }
    

    Since both match statements use this same condition, it will always be true or always be false on all.

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