Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting Firebase Firestore security rules so only users can CRUD their own data and all else is ignored

I have an app that is designed so authenticated users via Google only have access to their own data with no "social" features. I want to know the security rules for the below criteria.

Let's say I have 5 collections and one of them is called "todos" and the data mirrors the other collections in that it has a field for the authenticated users uid. The typical document looks something like this:

Todos

todo:{
  title:"some titled",
  body:"we are the world , we are the children",
  uid:"2378y4c2378rdt2387btyc23r7y"  
}

Some other collection

thing:{
  name:"some name",
  content:"Some content",
  whatever:"whu-eva",
  uid:"2378y4c2378rdt2387btyc23r7y"  
}

I want the authenticated Google user to be able to CRUD any data that has said users uid in the uid field. I want all other data to be inaccessible to the logged in user.

I want to know how to create rules for this scenario.

I'm mulling through the documentation now but I figure I might be able to save some time by asking. I do not have specific roles for the app. https://firebase.google.com/docs/firestore/solutions/role-based-access

As a side note, is their a feature in Firebase to automatically bind an authenticated Google users uid to documents created while they are logged in? (I am assuming the answer is no and I was planning on manually grabbing the uid in my app and setting it on the client prior to document creation).

Thank you.

Update

I tried using the code that Klugjo posted below.

When I try to test it in the simulator I get an error.

Here is my collection and a screenshot of the error.

enter image description here

enter image description here

Here is something else I tried: enter image description here

Based on everything I've read it seems like the following code should work - but it doesn't. I've supplemented the key "userId" in place of " uid" that is written in the object data at the top of this post. I changed the key to distinguish it from the uid.

service cloud.firestore {
  match /databases/{database}/documents {
    match /todos/{id} {
    allow read: if request.auth.uid == request.resource.data.userId;
    allow create, update, delete:
        if request.resource.data.userId == request.auth.uid;
      }
    }
  }

I've created a video where I try to GET and CREATE a document. I don't think I am using the testing feature correctly.

Video

https://www.youtube.com/watch?v=W7GZNxmBCBo&feature=youtu.be

EDIT

I have it working when I test with a hard-coded request.auth.uid. In the image below I hardcoded "test" as the request.auth.uid.

My problem now is that I would really like to know how to test it in the rules editor without hard-coding this information.

enter image description here

Edit

Here is a video demo of the problem using a real app.

https://www.youtube.com/watch?v=J8qctcpKd4Y&feature=youtu.be

like image 613
William Avatar asked May 16 '20 04:05

William


2 Answers

Here is a sample secure rule set for your requirements.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    match /users/{id}/{u=**} {
      allow read, write: if (isSignedIn() && isUser(id));
    }

    match /todos/{id}/{t=**} {
      allow read, write: if (isSignedIn() && isUserOwner());
    }

    match /{document=**} {
      allow read, write: if false;
    }



    function isSignedIn() {
      return request.auth != null;
    }

    function isUser(uid) {
      return uid == request.auth.uid;
    }

    function isUserOwner() {
      return getResourceData().uid == request.auth.uid;
    }

    function getResourceData() {
        return resource == null ? request.resource.data : resource.data
    }

  }
}

All documents are publicly inaccessible.

The rests will be decided based on the data already saved in DB and / or the data being sent by the user. The key point is resource only exists when reading from DB and request.resource only exists when writing to DB (reading from the user).

Documents under todos can be read and written only if they have a saved uid which is the same as the sent request's uid.

Documents under users can be read and written only if their document id is the same as the sent request's uid.

isSignedIn() function checks if request is authorised.

isUser(id) function checks if id matches the authorised request's uid.

isUserOwner() function checks if document's uid matches the authorised request's uid.

like image 143
cerkiner Avatar answered Oct 23 '22 23:10

cerkiner


I think what you are looking for is the "resource" parameter in the security rules: https://firebase.google.com/docs/firestore/security/rules-conditions#data_validation

Try something like:

service cloud.firestore {
  match /databases/{database}/documents {
    match /todos/{id} {
      allow read, write: if request.auth.uid == resource.data.userId;
    }
  }
}

EDIT:

Subcollection strategy

If you change your DB to look like the following:

/users/{userId}/todos/**

then you could allow users to read/write anything under their own document with the following rule:

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

This would have the advantage of not needing to introspect the contents of the data which I believe might count against your read quota.

like image 32
Gustavo Hoirisch Avatar answered Oct 23 '22 22:10

Gustavo Hoirisch