Firestore Security rules

We define the security rules in the Firebase console or in a firestore.rules file. Firebase doesn't bill reads and writes denied by security rules.

rules version

rules_version = "2"

firestore scope

We start by scoping the rules to cloud.firestore

service cloud.firestore {
    // ...
    }

database scope

We scope the rules to the current database. This is boilerplate code: we don't use the database wildcard.

match /databases/{database}/documents {
    // ...
}

set rules for a given collection

We target a collection. The document ID wildcard variable holds the requested document ID. We can, for example, compare the user document's ID with the authentication data.

match /users/{user_id}{
    	// ...
}

operations and condition

allow operation, operation: if condition;

operations

read
create
update
delete

authentication, user ID

If the user is not authenticated, request.auth is null. We may filter out unauthenticated users:

allow read: if request.auth != null;

The user's authentication uid (if logged-in) is available as request.auth.uid:

request.auth.uid

Note: if auth is null, trying to read uid triggers a failsafe mechanism that denies the request.

green-light specific documents

We green light the document if its ID matches a criteria:

    match /players/{player_id} {
         allow read: if request.auth.uid == player_id;
    }

We green light the document if its field matches a criteria. resource.data represents the requested document. For example, we check the document's owner property against auth.uid.

    match /planets/{planet_id} {
         allow read: if request.auth.uid == resource.data.owner.id;
    }

If the document is missing the field, the request is denied.

get authorization information in a separate document

We read a different document with get(). It is a billed read.

get(/databases/$(database) / documents / users / $(request.auth.uid)).data.rank

This unlocks a pattern where we read some authorization data in a different document, such as the user document, which would store the user's entitlements or ranks. This may not be a good architecture.

For example, to require a specific rank:

    match /characters/{character_id} {
         allow update: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.rank == "Game Master";
    }

For example, we enforce that the requested character's zone is the same as the player's character's zone

match /overworld_characters/{overworld_character} {
     allow read: if get(/databases/$(database)/documents/characters/$(request.auth.uid)).data.zone == resource.data.zone;
}

payload validation

request.resource.data is the request's payload. We validate critical fields such as the document's owner.

request.resource.data.age > 0

  // A) user sends a post that mentions himself as uid. check the uid field.
  allow create : if request.auth.uid == request.resource.data.uid;

  // B) user modifies a post that mentions himself as uid
  // A) check the uid field.
  allow update,delete: if
  request.auth.uid == resource.data.uid
  &&
  request.auth.uid == request.resource.data.uid;

Note: We can instead forbid writes coming from the client and perform validation in a Cloud Function with TypeScript.

earlymorning logo

© Antoine Weber 2026 - All rights reserved

Firestore Security rules

We define the security rules in the Firebase console or in a firestore.rules file. Firebase doesn't bill reads and writes denied by security rules.

rules version

rules_version = "2"

firestore scope

We start by scoping the rules to cloud.firestore

service cloud.firestore {
    // ...
    }

database scope

We scope the rules to the current database. This is boilerplate code: we don't use the database wildcard.

match /databases/{database}/documents {
    // ...
}

set rules for a given collection

We target a collection. The document ID wildcard variable holds the requested document ID. We can, for example, compare the user document's ID with the authentication data.

match /users/{user_id}{
    	// ...
}

operations and condition

allow operation, operation: if condition;

operations

read
create
update
delete

authentication, user ID

If the user is not authenticated, request.auth is null. We may filter out unauthenticated users:

allow read: if request.auth != null;

The user's authentication uid (if logged-in) is available as request.auth.uid:

request.auth.uid

Note: if auth is null, trying to read uid triggers a failsafe mechanism that denies the request.

green-light specific documents

We green light the document if its ID matches a criteria:

    match /players/{player_id} {
         allow read: if request.auth.uid == player_id;
    }

We green light the document if its field matches a criteria. resource.data represents the requested document. For example, we check the document's owner property against auth.uid.

    match /planets/{planet_id} {
         allow read: if request.auth.uid == resource.data.owner.id;
    }

If the document is missing the field, the request is denied.

get authorization information in a separate document

We read a different document with get(). It is a billed read.

get(/databases/$(database) / documents / users / $(request.auth.uid)).data.rank

This unlocks a pattern where we read some authorization data in a different document, such as the user document, which would store the user's entitlements or ranks. This may not be a good architecture.

For example, to require a specific rank:

    match /characters/{character_id} {
         allow update: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.rank == "Game Master";
    }

For example, we enforce that the requested character's zone is the same as the player's character's zone

match /overworld_characters/{overworld_character} {
     allow read: if get(/databases/$(database)/documents/characters/$(request.auth.uid)).data.zone == resource.data.zone;
}

payload validation

request.resource.data is the request's payload. We validate critical fields such as the document's owner.

request.resource.data.age > 0

  // A) user sends a post that mentions himself as uid. check the uid field.
  allow create : if request.auth.uid == request.resource.data.uid;

  // B) user modifies a post that mentions himself as uid
  // A) check the uid field.
  allow update,delete: if
  request.auth.uid == resource.data.uid
  &&
  request.auth.uid == request.resource.data.uid;

Note: We can instead forbid writes coming from the client and perform validation in a Cloud Function with TypeScript.