Firebase basics: protection and regulation of Database using Security Rules

Database can't work without some certain rules. For example, you need to decide who can make changes to a specific node or view certain data (you don't want that one user is able to see the correspondence of another users), what kind of data can be written to a specific field such as don't allow users in the column "Birthday" to enter text, only numbers are letter and etc.

Security rules exists for it. First, don't confuse the security rules and the Database Firebase rules. The rules are in a separate location from the base in the Firebase console, although they look similar (this is also JSON).

Types of security rules:

Types
.read Describes if and when data is allowed to be read by users.
.write Describes if and when data is allowed to be written.
.validate Defines what a correctly formatted value will look like, whether it has child attributes, and the data type.
.indexOn Specifies a child to index to support ordering and querying.

In the Firebase console It can look like this. As we can see, we gave access to the database to all information, including reading and making changes absolutely to any user, and also have established that the data can be of any type (put everywhere true). NEVER DO IT.

In the Firebase console, when you create a new project, it will be installed by default:

According to the specified rules, only users who are authorized (means they are registered by your application through Firebase Auth) can make changes and read data of the whole database. But this, as a rule, is not enough. You'd better define access in more detail. Consider the following example. There is a database.

{
    "users: {
        "1": {
            "name": "David,
            "bio": "Hello, I'm a...",
            "profileImg": "https://..."
        },
        "2": {
            "name": "Alice,
            "bio": "I work as...",
            "profileImg": "https://..."          
        },
        "events": {
            "fm": {
                "name": "Firebase Meetup",
                "date": 1511679035
            }
        },
        "eventAttendees": {
            "fm": {
                "1": "David",
                "2": "Alice"
            }
        }
    }
}

Let's take a look on the rule for this database.

{
  "rules": {
        "users":  {
            ".read": true,
            ".write": true,
            ".validate": true
        }
    }
}

This rule allows reading, making changes to the Users node to any person. Note, it is the node (users), not the sub-nodes and data fields (for example, users /1).

Wildcards.

To have access right to the subnodes (users/1, users/2 etc.) and in generall for the specification of access there are wildcards. it's variables that can contain any value you defined. Let's take a look this next rule:

{
  "rules": {
        "$uid": {
            "users":  {
                ".read": true,
                ".write": true
            }
        }
    }
}

As we can see, we included the variable $ uid. The $ sign means that it is a variable of the kind wildcards, uid is just the name of the variable (you can call it anything). This rule means that the user can accesses the sub-nodes of the Users node (see the base), for example, he can request information users / 1 (user data with the name David), in which case $ uid will contain the number 1, or users / 2 (User named Alice) ($ uid will contain number 2). Now we can change the data in any of the subnodes of the Users node. For example, let's change David's user data located at users / 1.

const uid = '1'
firebase.database().ref('users/' + uid).set({
    name: 'Dave',
    bio: 'Programmer',
    profileImg: 'http://...'
});

Here we created variable uid an put it here 1. Next, in user/ + uid we change the data fields. For example, now David was replaced with Dave.

Server Variables.

Server Variables are the variables, which we can use in Security Rules. There are only 6 of them.

Types
auth A variable containing the token payload if a client is authenticated, or null if the client isn't authenticated
data Existed data
newData The data that will result if the write is allowed
now Current time
root the current data at the root of your Firebase Realtime Database
$uid wildcard variable

Let's apply some of them

{
  "rules": {
        "$uid": {
            "users":  {
                ".read": "auth.uid == $uid",
                ".write": "auth.uid == $uid",
                ".validate": "newData.exists()"
            }
        }
    }
}

This rule means that first of all, only registered users have access to sub-nodes of User node, and only to those sub-nodes that coincide with their registered identificational number.

Providing that the User David has the registration number 1. Accordingly, he has access only to users/1, that means only to their personal information. Other users will not have access to this sub-nodes (users/1) because their registration number will be different. They will have access to their sub-nodes.

In the case with .validate and newData.exists (), we essentially say that you can not perform a data entry operation without the data itself (the data must exist). Literally, allow the data entry operation only if data is available. This allows you to prevent cases when users, when publishing, for example, a new blog post, do not write anything, and press the button to "post a blog post." We can also write the following

{
  "rules": {
        "$uid": {
            "users":  {
                ".read": "auth.uid == $uid",
                ".write": "auth.uid == $uid",
                ".validate": "newData.child('name').isString()"
            }
        }
    }
}

In this case we tell that the sub-nodes data field (name) of the Users should be the text (isString()). Now let's establish that other data fields can also be text only, and also that the Biography field can not be more than 500 characters.

{
  "rules": {
        "$uid": {
            "users":  {
                ".read": "auth.uid == $uid",
                ".write": "auth.uid == $uid",
                ".validate":         
                    "newData.child('name').isString() &&
                    newData.child('bio').isString() &&
                    newData.child('name').val().length < 500 &&
                    newData.child('profileImg').isString()"
            }
        }
    }
}

In this rules, if we try to enter this data (the below), we will receive the rejection, because in ProfileImg field has the number 703 , and we identified that this should be text.

"2": {
    "name": "Alice",
    "bio": "Lorem ipsum...",
    "profileImg": 703
}

But this information will pass, that means in profileImg field text will be shown

"2": {
    "name": "Alice",
    "bio": "Lorem ipsum...",
    "profileImg": "https://..."
}

It's enough for beginning. The topic of the rules is quite extensive, and in the next article we will consider the rules for Firebase Storage.

Links:

  1. Firebase types of rules
  2. Firebase variables