Firebase Firestore User-Based Security

The purpose of this article is to explain a simple way to set the Firebase Firestore security rules to allow users to only CRUD their data and not everything else.

Let’s say you have 2 collections and one of them is “users” with user data and the other one is called “todos” for documents owned by users. The typical document looks like this:

Users

user: {
  name:"john doe"
}

Todos

todo: {
  title:"hello to do",
  body:"we are the world , we are the children",
  uid:"65sd424c237a23a387b54543r79"  
}

You want users to be able to CRUD their own data in both of these collections. That means authorized requests can create, read, update or delete a user document only if the document is assigned to them. Similar is valid for the todos collection; authorized requests can create, read, update or delete todo documents only if the document has their uid.

Here is a sample secure rule set for these 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
    }

  }
}

In this example, normally, all documents are publicly inaccessible. In users and todos collections, we have specific rules and accessibility will be decided based on the data already saved in DB and / or the data being sent by 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.

getResourceData() function returns resource data. We need this to simplify ti if conditions. The key point is resource only exists when reading from DB and request.resource only exists when writing to DB (reading from the user).

I hope this can provide a good starter for the ones developing secure firebase applications. This is a simplified solution but this scenarios can get quite complicated for bigger databases with more interconnected data structure.