Firebase Cloud Firestore permissions: don't do "allow read, write: if true;"
Tags: react, reactnative, firebase, cloudfirestore
Published: 2020-08-17 13:30:16

Say you're using Firebase with Cloud Firestore to handle user login and registration for a React Native app. You have the following handler for a user registration button: (Credit: [this freeCodeCamp tutorial](https://www.freecodecamp.org/news/react-native-firebase-tutorial/)).

```javascript
const onRegisterPress = () => {
        if (password !== confirmPassword) {
            alert("Passwords don't match.")
            return
        }
        firebase
            .auth()
            .createUserWithEmailAndPassword(email, password)
            .then((response) => {
                const uid = response.user.uid
                const data = {
                    id: uid,
                    email,
                    fullName,
                };
                const usersRef = firebase.firestore().collection('users')
                usersRef
                    .doc(uid)
                    .set(data)
                    .then(() => {
                        navigation.navigate('Home', {user: data})
                    })
                    .catch((error) => {
                        alert(error)
                    });
            })
            .catch((error) => {
                alert(error)
        });
    }
```

The `firebase.auth().createUserWithEmailAndPassword()` method is called to perform the actual user creation. After hitting the button you can see your new user being added to the Firebase console:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6or2yubzp9nryjrpt7x8.png)

But what if you hit the following error?

```
FirebaseError: [code=permission-denied]: Missing or insufficient permissions
```
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/rq0w66vwnnb4ebipgnni.png)

Many [top-voted answers](https://stackoverflow.com/questions/56510745/firebaseerror-code-permission-denied-missing-or-insufficient-permissions) on StackOverflow recommend setting unsafe rules to solve the problem. Here's a common example:

```
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if true;
    }
  } 
}
```
This is bad. `allow read, write: if true;` does exactly what it says: It allows everyone (yes, everyone on the internet) to read and write to any document in your Firebase store. **This is not appropriate for production**.

Despite these warnings, such answers still float to the top of every StackOverflow thread on the topic, for the simple reason that it "solves" the problem for "testing purposes". But what happens after "testing"?

I found the mess of answers and the official [Firebase documentation](https://firebase.google.com/docs/rules/basics) somewhat confusing to wade through. I hope the following will help.

## Where to set rules?
It wasn't obvious to me. They are here (ensure you are in **Cloud Firestore** and not Realtime Database):

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/5m2l5kzqfzk7mq7lqwqd.png)

Let's look at some of the suggested solutions from StackOverflow or the [Firebase documentation](https://firebase.google.com/docs/rules/basics) and see what each is actually doing:

## Default: allow open access for 30 days
```
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if
          request.time < timestamp.date(2020, 9, 16);
    }
  }
}
```
This is the default rule set that you are given when you set up your Firebase project and add a Cloud Firebase database: it allows open access to everyone for 30 days, and then will deny access to everyone.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/zwbif0vb83u0bc03fzyu.png)

Some answers suggest simply pushing this date forward. This is clearly as bad as setting `allow read, write: true`. This is not a permanent solution.

## Allow read/write by any authenticated user

Another common suggestion is this:

```
// Allow read/write access on all documents to any user signed in to the application
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.auth != null;
    }
  }
}
```

Better, if you are comfortable with any authenticated user being able to read and write to anything. However, I am making a registration handler - which means anyone can make an account and become an authenticated user. Let's keep looking.

## Content-owner only access

Firebase documentation then suggests this for content-owner only access:

```
service cloud.firestore {
  match /databases/{database}/documents {
    // Allow only authenticated content owners access
    match /some_collection/{userId}/{documents=**} {
      allow read, write: if request.auth != null && request.auth.uid == userId
    }
  }
}
```

Seems perfect, right? Except this literal rule won't work for my registration handler either. A mindless copy-paste won't do here: `some_collection` does not exist. In fact, in a new Firebase Cloud Firestore, no collections exist:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/2dcbyoygf2aor0v1d5se.png)

If you recall from the handler above, the `then()` callback accesses a Firestore collection called `users`:

```
const usersRef = firebase.firestore().collection('users')
                usersRef
                    .doc(uid)
                    .set(data)
```

So the final [non-obvious step](https://stackoverflow.com/questions/63272544/how-do-i-set-up-my-firebase-firestore-rules-for-my-personal-portfolio-website) is to ensure that your rule and the `firebase.firestore().collection()` call are actually referencing the same collection.

## The collection doesn't need to exist; you just need a rule matching it
There is no need to create an empty `users` collection ahead of time. The `firebase.firestore().collection('users').doc(uid).set(data)` call simply has to find a matching ruleset. In this case, the match is `/users/{userId}/{documents=**}`.

If the `users` collection does not exist, it will be created.

Note that a typo (`collection('Users')`, `collection('user')`) would result in a permission error - **not because the collection doesn't already exist**, but because there is **no matching ruleset to allow the write**.

## You can separate read and write rules
And finally, read and write rules can be separated into their own conditions. For example, the following will allow any authenticated user to read data for any document in the `users` collection. But they can only write to (create/update/delete) their own:

```
service cloud.firestore {
  match /databases/{database}/documents {
    // Allow only authenticated content owners access
    match /users/{userId}/{documents=**} {
      allow write: if request.auth != null && request.auth.uid == userId;
      allow read: if request.auth != null;
    }
  }
}
```

Lastly, I recommend looking at your newly created documents to understand their structure:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/h2dsjnbw3wgofabymfr3.png)

## Use the Rules Playground to test rules
Note in this example, authentication with uid=`jill` cannot write to the path `users/jack`. The line responsible for the write deny is highlighted:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/gdft9tn0dlu7mvb84i9b.png)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/774yunyuihkm0myvgg19.png)

But authentication with uid=`jill` can read from path `users/jack`, and the line allowing this is highlighted as well:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/g9d4ps7klw0dogk0bx8e.png)

## No more nuclear option
I hope this helped clarify use of Cloud Firestore rules, and allows you to steer away from the unnecessarily broad `allow read, write: if true;` option. Please feel free to leave comments below.