Securing multi-path updates in Firebase

Denormalizing data in Firebase is “normal”, so let’s secure it.

Consider the common scenario of users and groups:

{
"users": {
"user1": {
"groups": {
"group1": true,
"group2": true
}
}
},
"groups": {
"group1": {
"users": {
"user1": true,
}
},
"group2": {
"users": {
"user1": true,
}
}
}
}

When a user joins a group, two things must happen:

  1. The user should be added to the group’s user[] list
  2. The group should be added to the user’s group[] list

How can we enforce that the client performs both steps when adding themselves to a group? Enter security rules…

Security Rules

Consider the following security rules:

{
"users": {
"$userId": {
"groups": {
"$groupId": {
".read": true,
".write": "newData.parent().parent().parent().parent().child('groups').child($groupId).child('users').hasChild($userId)"
}
}
}
},
"groups": {
"$groupId": {
"users": {
"$userId": {
".read": true,
".write": "newData.parent().parent().parent().parent().child('users').child($userId).child('groups').hasChild($groupId)"
}
}
}
}
}

The .write rules in each node are effectively saying, “Do not allow a key here that doesn’t exist in the other node”.

To learn more about newData, traversing with parent/child, and many more tools to help you lock it down, refer to the official Firebase Security Rules API.