blog | 26 May, 2023
Firebase: Cheatsheet for Firestore security rules
Explore this guide for a range of security rules, from beginner to advanced levels, complete with illustrative examples.
Understanding resource
and request
Firestore provides access to a range of special variables utilized for rule composition. Two important variables to understand before continueing are request
and resource
request
encompasses incoming data, including authentication details and timestamps.resource
refers to the existing data being requested.
It's important to note that the request object also contains a parameter named resource. To avoid the confusion between resource
and request.resource
we can write helper methods for identification.
function existingData() {
return resource.data;
}
function incomingData() {
return request.resource.data;
}
These methods can then be used to define the security rules. Here's a silly example:
match /products/{productId} {
allow update: if existingData().locked == false;
allow delete: if incomingData().price == 10;
}
Basic security rules
1. Read and write all documents
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}
2. Lock all documents
match /{document=**} {
allow read, write: if false;
}
3. Allow access to documents in a collection
match /posts/{documentId} {
allow read: if true; // Allows anyone to read documents in this collection
}
4. Allow access to all collections except one
match /{document=**} {
allow read, write: if false;
}
match /{collection}/{docId} {
allow read: if collection != 'posts';
}
5. Check if write request includes a field
match /users/{documentId} {
allow write: if request.resource.data.name != null;
// Ensures 'name' field exists in the write request
}
6. Check if the field exists and its type in a write request
match /users/{documentId} {
allow write: if request.resource.data.name is string;
// Ensures 'name' field exists in the write request and its type is string
}
7. Check if the value exists in an array property of a document
match /users/{documentId} {
allow read: if
request.resource.data.book
in resource.data.books
// resource.data.books is of type array
}
8. Allow access if the request does not contain a field
match /users/{userId} {
allow update: if !('age' in request.resource.data);
}
9. Ensure that the request data does not alter a specific field
match /users/{userId} {
allow update: if request.resource.data.age == resource.data.age;
}
10. Only allow updating specific fields
match /users/{userId} {
allow update: if request.resource.data.diff(resource.data).changedKeys().hasOnly(['email', 'phone']);
}
11. Deny access after a certain duration
match /posts/{postId} {
allow update: if request.time < resource.data.createdAt + duration.value(100, 's');
// Notice that we are utilizing the createdAt timestamp from the document
}
12. Make a document read-only after certain duration
match /posts/{postId} {
allow read: if true; // Always allowing read
allow update, delete: if request.time < resource.data.createdAt + duration.value(180, 's');
// Allow update only if the current time is within 180 seconds of the document's creation time
}
13. Allow access only before a certain date
match /posts/{postId} {
allow read, write: if request.time < timestamp.date(2020, 6, 22);
}
14. Allow access if request contains any of the attributes
match /posts/{postId} {
allow write: if request.resource.data.keys().hasOnly(['field1', 'field2']);
}
14. Limit array modifications to incremental updates
allow write: if request.resource.data.arrayField.size() == resource.data.arrayField.size() + 1
&&
(
request.resource.data.arrayField[request.resource.data.arrayField.size() - 1]
== (resource.data.arrayField[resource.data.arrayField.size() - 1] + 1)
);
User and role based rules
1. Allow access only when authenticated
match /posts/{postId} {
allow read: if request.auth != null;
}
2. Allow access to only the owner of the document
match /posts/{postId} {
allow write: if
request.auth.uid == resource.data.owner_id;
}
3. Allow access only when user's email is verified
match /posts/{postId} {
allow write: if request.auth.token.email_verified;
}
4. Allow access based on user's authentication provider
match /posts/{postId} {
allow read: if request.auth.token.firebase.sign_in_provider == 'google.com';
}
5. Deny operations to anonymous users
match /posts/{postId} {
allow create: if request.auth.uid != null
&& request.auth.token.firebase.sign_in_provider != 'anonymous';
}
6. Allow acces when BelongsTo/HasOne relation exists
match /posts/{userId} { allow read: if
request.auth.uid == userId;
}
Or we can create a method that would work on all collection documents
match /posts/{userId} { allow read: if belongsTo(userId)
;
}
function belongsTo(userId) { return request.auth.uid == userId }
7. Allow acces with HasMany relation
match /posts/{postId} {
allow write: if requestMatchesUID();
allow update: if requestMatchesUID() && resourceMatchesUID();
allow delete: if resourceMatchesUID();
}
function requestMatchesUID() {
return request.auth.uid == request.resource.data.uid;
}
function resourceMatchesUID() {
return request.auth.uid == resource.data.uid;
}
8. Allow write acces to user with attribute isAdmin
match /posts/{postId} {
allow write: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.isAdmin == true;
allow read: true;}
9. Allow access to users based on their role
match /posts/{postId} {
allow read: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == "Reader"
allow write: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == "Writer"
}
10. Allow access to users based on array of roles
match /posts/{postId} {
allow read: if get(/databases/$(database)/documents/users/$(request.auth.uid))
.data.roles.hasAny(["Reader"]);
allow write: if get(/databases/$(database)/documents/users/$(request.auth.uid))
.data.roles.hasAny(["Writer", "Admin"]);
}
11. Allow access to admin or the list of allowed users
match /posts/{postId} {
allow write: if request.auth.token.role == 'admin' || (request.auth.uid in resource.data.allowedUsers);
}
12. Limit access to users signed in before a certain date
match /posts/{postId} {
allow read: if request.auth.time < timestamp.date(2023, 12, 12);
}
Common functions
function isSignedIn() {
return request.auth != null;
}
function emailVerified() {
return request.auth.token.email_verified;
}
function userExists() {
return exists(/databases/$(database)/documents/users/$(request.auth.uid));
}
function isUser(userId) { return request.auth.uid == userId; }
function getUserData() { return get(/databases/$(database)/documents/accounts/$(request.auth.uid)).data }