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.