Lesson 14 / 25

Common Rule Patterns

Owners, roles and validation.

Functions, claims and field checks

Reusable functions keep rules readable. Owner-only access compares the path wildcard or a stored field with request.auth.uid. Roles come from custom claims (request.auth.token.admin == true) or from a document read with get() / exists(), which count as extra reads and are limited per request (check the docs). Validation checks types and shapes: request.resource.data.keys().hasOnly([...]) blocks unknown fields, diff(resource.data).affectedKeys().hasOnly([...]) limits which fields an update may change, and comparisons such as request.time == request.resource.data.createdAt force server timestamps. Validation in rules complements, not replaces, validation in your UI.

Owner, admin and validated writes

Firestore Security Rules.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    function signedIn() { return request.auth != null; }
    function isOwner(uid) { return signedIn() && request.auth.uid == uid; }
    function isAdmin() { return signedIn() && request.auth.token.admin == true; }

    match /profiles/{uid} {
      allow read: if signedIn();
      allow create: if isOwner(uid)
        && request.resource.data.keys().hasOnly(['displayName', 'bio', 'createdAt'])
        && request.resource.data.displayName is string
        && request.resource.data.displayName.size() <= 50
        && request.resource.data.createdAt == request.time;
      allow update: if isOwner(uid)
        && request.resource.data.diff(resource.data).affectedKeys()
             .hasOnly(['displayName', 'bio']);
      allow delete: if isAdmin();
    }

    match /orgs/{orgId}/projects/{projectId} {
      allow read, write: if signedIn()
        && exists(/databases/$(database)/documents/orgs/$(orgId)/members/$(request.auth.uid));
    }
  }
}

Protect role fields

If roles live in a user document, make sure users cannot update that field themselves, or a single write makes anyone an admin.

Quick check: Which expression stops an update from changing fields other than displayName and bio?

  • allow update: if true
  • resource.data.keys().size() == 2
  • request.auth.token.admin == true
  • request.resource.data.diff(resource.data).affectedKeys().hasOnly(['displayName', 'bio'])
Answer

request.resource.data.diff(resource.data).affectedKeys().hasOnly(['displayName', 'bio']) — diff().affectedKeys() lists changed fields.