I just tested out this simple security rule:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function isAuthor() {
return request.auth != null &&
request.auth.uid == resource.data.author.uuid;
}
match /posts/{postId} {
allow read, write: if isAuthor();
}
}
}
With a simulated read operation and an "incorrect" UID, I read a document 100 times and it was denied 100, and it counted towards the billing 100 times as read operations as you can see in this spike.

So Yes, using resource
is costly. No matter if it is denied or passed, it'd count towards your billing and you are subject to billing fraud due to Firebase's naturally amazing system.
You'd need to implement a lot of cloud workarounds to mitigate this in production with many users since there's no straight solution or one way to protect against such attacks.
Edit:
Official Firebase Docs says
The resource is the current value within the service represented as a map of key-value pairs. Referencing resource
within a condition will result in at most one read of the value from the service. This lookup will count against any service-related quota for the resource. For get requests, the resource will only count toward quota on deny.
That means that if your condition does not reference resource
, it will not get counted; it is also more accurate to say that if your condition does not access the resource
field, it will not be counted. Bear in mind that even if you accessed resource and your rule succeeded, you'd get only 1 read operation at most as mentioned, not two. It only counts on deny as we've already tested.
(1) This will deny and not count towards your reads:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false && resource.data
}
}
}
(2) This will deny but count towards your reads because you accessed resource
.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if resource.data.readable; // always false
}
}
}
(1) Resulted in 0 usage with 100 denied requests.
(2) Resulted in 100 usage with 100 denied requests.
Here's a tip to know if you're going to get billed a read operation or not.
In the Rules Playground:
If you see a resource
on the right, that means this rule has accessed resource
, which means it billed you a read operation.
Such as:
And (.readable
is false
in the document):

If you don't see resource
on the right, that means this rule has not accessed resource
, and thus hasn't billed you a read operation.
Such as (resource.data
is always true
):

References:
- https://firebase.google.com/docs/rules/rules-language#basic_structure