Skip to content

Instantly share code, notes, and snippets.

@peteristhegreat
Last active January 11, 2023 04:37
Show Gist options
  • Save peteristhegreat/8afd9f42fd7f1d96b2a5b100aca74d3c to your computer and use it in GitHub Desktop.
Save peteristhegreat/8afd9f42fd7f1d96b2a5b100aca74d3c to your computer and use it in GitHub Desktop.
Firebase Firestore Security Rules, gotchas

Introduction

After using firestore for a larger project and synthesizing some best practices into my use cases, I have my way of explaining design choices.

Firebase Firestore

Db Design

When drilling into layers of the db, you have to alternate between collections (typically arrays of generated ids) and documents (dictionaries).

Top level is a collection of collections.

Then in that list you have your drill down into a single collection.

A single document in a collection can have dictionary data, and it can contain more collections.

Or in other words:

document:
  dictionary data
  collection1 (named id)
  collection2
  collection3
  ...

collection:
  document1 (named id, or generated ids)
  document2
  document3
  ...

Then when you drill thru layers of your db a pattern emerges in the paths:

dictionary_data = db.collection("toplevel1").document("document1").collection("nested-collection1").document("nested-document1").get()

Collection, Doc, Collection, Doc, Collection, Doc, etc.

Time Series data

Just make a collection and use add and include a timestamp. If you use a Firestore style timestamp, it is easily readable in the admin db editor.

const modified = firebase.firestore.Timestamp.now()
// or
const modified = firebase.firestore.Timestamp.fromDate(new Date())
// or
import { serverTimestamp } from "firebase/firestore";
timestamp: serverTimestamp()

https://firebase.google.com/docs/reference/unity/struct/firebase/firestore/timestamp#public-static-functions

SaaS Db Design

For a multitenant architecture, having the data structured well initially helps a lot.

/users/{userId}/
                user-data (dict)

/teams/{teamId}/projects/{projectId}/
                                     project-data (dict)

/metadata-teams/{teamId}/transactions/{transactionId}/
                                                     transaction-data (dict)
                       /metadata-projects/{projectId}/
                                                     project-metadata (dict)
                        team-metadata (dict)

/history-teams/{teamId}/history-projects/{projectId}/{history-type}/
                                                            history-data (dict)
                                             project-history-data (dict)

/feedback/{issueId}/
                    issue-data (dict)

Including a modified and modifiedBy stamp on each level makes reports a lot easier later.

Shape of the db is strongly related to roles or things accessible by who or which group of users or backend only.

Also don't duplicate collection names if possible... This makes for cross db collection group queries easier.

If you missed this design step earlier, then you can work around it by adding a boolean into each document in a specific directory to describe where it is in the dictioanry.

E.g.

/metadata-teams/{teamId}/projects/{projectId}
                                           "metadataTeamsProjects": true
and

/teams/{teamId}/projects/{projectId}
                                    "teamsProjects": true

https://firebase.blog/posts/2019/06/understanding-collection-group-queries

Firestore Security Rules

When writing firestore security rules, the match syntax is unaware of the dictionary data.

It is only aware of the collection names and the document names.

This makes it so that when you are first writing rules it can be easy to think,

"I'm drilling thru all my levels of my nested dictionary including my final dict layer".

WRONG. The final layer is referenced as incomingData() or request.resource.data.

The wild card of {document**} with rules v2, doesn't touch the final dict layer, only the collection names and document names.

The blog from google discussing the higher precision for filtering on incoming data, too, makes this topic more clear.

https://firebase.blog/posts/2021/01/code-review-security-rules

Custom Claims

Be sure to plan a way to associate a user to their roles in the custom claims stored in the login token. The verification of claims in the firebase rules is quick and easy.

Setting a claim needs to be done by an admin sdk such as a backend or in the firebase functions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment