Firebase Realtime Database security rules are how you secure your data from unauthorised users and protect your data structure.
In this quick tip tutorial, I will explain how to configure your database security rules properly so that only authorised users have read or write access to data. I'll also show you how to structure your data to make it easy to secure.
The ProblemLet's assume we have JSON data in our Firebase database, as in the example below:
{"users" : {
"user1" : {
"firstName" : "Chike",
"lastName" : "Mgbemena",
"age": "89"
"phoneNumber" : "07012345678"
},
"user2" : {
"firstName" : "Godswill",
"lastName" : "Okwara",
"age": "12"
"phoneNumber" : "0701234"
},
"user3" : {
"firstName" : "Onu",
"lastName" : 543,
"age": 90
"phoneNumber" : "07012345678"
},
...
}
}
Looking at the database, you can see that there are some issues with our data:
Two users ( user1 and user3 ) have the same phone numbers. We'd like these to be unique. user3 has a number for last name, instead of a string. user2 has only seven digits in their phone number, instead of 11. The age value for user1 and user2 is a string, while that of user3 is a number.With all these flaws highlighted in our data, we have lost data integrity. In the following steps, I will show you how to prevent these from occurring.
Permissive RulesThe Firebase realtime database has the following rule types:
Type Function .read Describes if and when data is allowed to be read by users. .write Describe 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.Read more about them in the Firebase docs .
Here is a very permissive rule for the users key in our database.
{"rules": {
"users": {
// users is readable by anyone
".read": true,
// users is writable by anyone
".write": true
}
}
}
This is bad, because it gives anyone the ability to read or write data to the database. Anyone can access the path /users/ as well as deeper paths. Not only that, but no structure is imposed on the users' data.
Access Control Rules {"rules": {
"users": {
"$uid": {
".read": "auth.uid == $uid",
".write": "auth.uid == $uid",
}
}
}
}
With these rules, we control access to the user records to logged-in users. Not only that, but users can only read or write their own data. We do this with a wildcard: $uid . This is a variable that represents the child key (variable names start with $ ). For example, accessing the path /users/user1 , $uid is "user1" .
Next, we make use of the auth variable, which represents the currently authenticated user. This is a predefined server variable supplied by Firebase. In lines 5 and 6, we're enforcing an accessibility constraint that only the authenticated user with the same id as the user record can read or write its data. In other words, for each user, read and write access is granted to /users/<uid>/ , where <uid> represents the currently authenticated user id.
Other Firebase server variables are:
now The current time in milliseconds since linux epoch. root A RuleDataSnapshot representing the root path in the Firebase database as it exists before the attempted operation. newData A RuleDataSnapshot representing the data as it would exist after the attempted operation. It includes the new data being written and existing data. data A RuleDataSnapshot representing the data as it existed before the attempted operation. auth Represents an authenticated user's token payload.Read more about these and other server variables in the Firebase docs .
Enforcing Data StructureWe can also use Firebase rules to enforce constraints on the data in our database.
For example, in the follow rules, in lines 8 and 11, we are ensuring rules that any new value for the first name and last name must be a string. In line 14, we make sure that age is a number. Finally, in lines 17 and 18, we're enforcing that the phone number value must be a string and of length 11.
{"rules": {
"users": {
"$uid": {
".read": "auth.uid == $uid",
".write": "auth.uid == $uid",
"firstName": {
".validate": "newData.isString()"
},
"lastName": {
".validate": "newData.isString()"
},
"age": {
".validate": "newData.isNumber()"
},
"phoneNumber": {
".validate": "newData.isString() &&
newData.val().length == 11"
},
}
}
}
}
But how do we prevent duplicate phone numbers?
Preventing DuplicatesNext, I'll show you how to prevent duplicate phone numbers.
Step 1: Normalize the Data StructureThe first thing we need to do is to modifying the root path to include a top-level /phoneNumbers/ node. So, when creating a new user, we will also add the user's phone number to this node when validation is successful. Our new data structure will look like the following:
{"users" : {
"user1" : {
"firstName" : "Chike",
"lastName" : "Mgbemena",
"age": 89,
"phoneNumber" : "07012345678"
},
"user2" : {
"firstName" : "Godswill",
"lastName" : "Okwara",
"age": 12,
"phoneNumber" : "06034345453"
},
"user3" : {
"firstName" : "Onu",
"lastName" : "Emeka",
"age": 90,
"phoneNumber" : "09034564543"
},
...
},
"phoneNumbers" : {
"07012345678": "user1",
"06034345453": "user2",
"09034564543": "user3",
...
}
} Step 2: Enforce New Data Structure
We need to modify the security rules to enforce the data structure:
{"rules": {
"users": {
"$uid": {
...
"phoneNumber": {
".validate": "newData.isString() &&
newData.val().length == 11 &&
!root.child('phoneNumbers').child(newData.val()).exists()"
},
}
}
}
} Here, we're making sure the phone number is unique by checking if it is already a child of the /p