Firebase Realtime Database Security Rules #2 - First Branch

Right now our Realtime Database Security Rules allow only one user, ourselves, to write to the database:

{
  "rules": {
    ".read": "auth.uid != null && auth.uid == '1234567890123456789012345678'",
    ".write": "auth.uid != null && auth.uid == '1234567890123456789012345678'"  
}

And the following data is in the database:

{
  "MyUsers" : {
    "firstName" : "Jane",
    "lastName" : "Smith",
    "userUID" : "1234567890123456789012345678" 
}

At this point we are able to write data as ourselves and we are preparing to create security rules that allow users to read and write their own user profile data.

So let’s begin building out rules for the first branch of the database tree.  Doing so means we won’t have rewrites in our future. And by writing rules now you will think earlier about how to structure your data: who will be reading the data?  Who will be writing the data? Who should or should not be able to see the data?

In our case the owners of the user profile - and only the owners of the user profile - should be able to read or write the data.

Let’s start by moving our rules to beneath the MyUsers branch.

{
  "rules": { 
    "MyUsers": {
      ".read": "auth.uid != null && auth.uid == 1234567890123456789012345678",
      ".write": "auth.uid != null && auth.uid == 1234567890123456789012345678"
}

Try to write to /MyUsers as our allowed user and it is successful:

I: Write to database is successful!

So, we can still write to the database as ourselves. No big surprises there.  Let’s keep going.

We just said that users who own the data should be able to read and write it but nobody else.  So let’s include the user UID in the database path. Luckily Google UID’s conform to the characters that can be included in a Realtime Database path so we can make use of this important piece of data.

Let’s add the UID into our path by adding another child to our database path and write the data again:

DatabaseReference dbRef1 = FirebaseDatabase.getInstance().getReference()
       .child("MyUsers")
       .child(mAuth.getUid().toString());

Delete the old data written directly under /MyUsers to have a tree that looks like:

{
  "MyUsers" : {
    "1234567890123456789012345678" : {
      "firstName" : "Jane",
      "lastName" : "Smith",
      "userUID" : "1234567890123456789012345678"
}

So let’s apply some new rules to lock down the user profile data for owning users.  


We’ll start by adding the new path level to our rules only this time we will not hardcode the path name but instead will use a $location variable in order to wildcard the userUID value as it comes in for evaluation:

{
  "rules": { 
      "MyUsers": {
          "$userUID": {
          ".read": "auth.uid != null && auth.uid == '1234567890123456789012345678'",
          ".write": "auth.uid != null && auth.uid == '1234567890123456789012345678'"              
}

Wildcarding the paths is just the beginning of the power of Realtime Database security rules. 

Now we can check the value of the userUID in the rules:

{
  "rules": { 
      "MyUsers": {
          "$userUID": {
          ".read": "auth.uid != null && auth.uid == $userUID",
          ".write": "auth.uid != null && auth.uid == $userUID"           
}

Write to the database for our user is successful still:

I: Write to database is successful!

Try logging in as another user and writing to the database (you may have to clear storage and cache on the app as Google SignIn will be sticky at this point).

The write is successful and you’ll now have another user object under MyUsers. Terrific!

In the next installment we’ll look at locking down the data at this path even more.