Cloud Firestore Security Rules are used to determine who has read and write access to collections and documents stored in Cloud Firestore, as well as how documents are structured and what fields and values they contain.
In this tutorial, We will learn how to secure your data using Firebase Authentication and Cloud Firestore Security Rules to handle serverless authentication, authorization, and data validation.
To set up and deploy your first set of rules, open the Rules tab in the Cloud Firestore section of the Firebase console.
Cloud Firestore populates a default rule, denying read and write access to everyone on all data:
service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read, write: if false; } } }
Match Path
Rules match
paths representing a document or collection in Cloud Firestore. Rules may match one or more paths and more than one rule can match the document name in a given request. The basic type of rule is the allow
rule, which allows read
and write
operations if an optionally specified condition is met. In most cases, you’ll want to write rules that apply to any document within a collection, not just a specific one. In these situations, you can use wildcards to specify any element within a path. A wildcard is created by adding curly brackets around a string.
match /thought/{anything}{ .... }
Apply rules to multiple documents or collections with wildcards. Wildcards represent the document or collection ID in the match path as a string between curly brackets. Add =**
to the end of your wildcard string to apply the wildcard to all documents and subcollections in the path.
match /thought/{anything=**}{ ..... }
The request variable
The request
variable is a map
that contains information about the request that’s coming in from the client. You can view the reference documentation for a full list of fields, but two fields you’ll frequently be working with are the auth
field, which contains information from Firebase Authentication about the currently signed-in user, and the resource
field, which contains information about the document being submitted. Common use cases for the request
object include:
- Confirming that
request.auth
is not null to ensure that a user is signed in - Using the value of
request.auth.uid
to get the user’s ID, as verified by Firebase Authentication - Using
request.resource.data.
to ensure the value being submitted for a document write meets a particular format. This is most common way to perform data validation within your database.
For example
match /bulletinBoard/{note} { // Anybody can read these messages, as long as they're signed in. allow read, write: if request.auth != null; } match /spaceships/{spaceship} { allow read; // Spaceship documents can only contain three fields -- a name, a catchy // slogan, and cargo capacity greater than 6500. // Only these three fields are allowed, and this will evaluate to false // if any of these fields are null. allow write: if request.resource.data.size() == 3 && request.resource.data.name is string && request.resource.data.slogan is string && request.resource.data.cargo is int && request.resource.data.cargo > 6500; }
Resource vs Request.resource variable
The resource
variable is similar to the request.resource
variable, but it refers to the document that already exists in the database. In the case of a read operation, this is the document that is about to be read, while in a write operation, this is the old document that is about to be updated or altered. Both the resource
and request.resource
objects are maps. Each contains a __name__
property indicating the document name, and a data
property where all user-defined properties are declared. By accessing keys within the data property of these maps, you can get the values of the various fields belonging to the document being referenced. For example, resource.data.kesselRun
is mapped to the value of the kesselRun
field within the document represented by the resource variable. Examples using the resource object.
match /databases/{database}/documents { match /bulletinBoard/{note} { // You can read any document that has a custom field named "visibility" // set to the string "public" or "read-only" allow read: if resource.data.visibility in ["public", "read-only"]; } match /products/{productID}/reviews/{review} { allow read; // A user can update a product reviews, but they can't change // the headline. // Also, they should only be able up update their own product review, // and they still have to list themselves as an author allow update: if request.resource.data.headline == resource.data.headline && resource.data.authorID == request.auth.userID && request.resource.data.authorID == request.auth.userID; }
Get Document From Database
Using the resource
variable will let you evaluate the current document that is about to be accessed, but sometimes you will want to evaluate other documents in other parts of the database. You can do so by calling the get()
function with the document path in the database. This will retrieve the current document, which will allow you to access custom fields through the data property, just like the resource object. One very common use of this function is to evaluate an access control list and determine if the userID
of the current user is part of that list. The exists()
function can also be used to determine if a document already exists. When entering a path into the exists or get function, you do not include quotes. If you want to include a variable in the path name, you should enclose the variable in parenthesis and preface it with a dollar sign. For example:
match /games/{game}/playerProfiles/{playerID} { // Every "game" document has the userID of a referee, who is allowed to // alter player profiles allow write: if get(/databases/$(database)/documents/games/$(game)).data.referee == request.auth.uid; // All players in a game are allowed to view the player profiles of any // other player. allow read: if exists(/databases/$(database)/documents/games/$(game)/playerProfiles/$(request.auth.uid)); } match /guilds/{guildID}/bulletinBoard/{post} { // Assume our guild document includes a "users" field, which itself is // a map consisting of a player ID and their role. For example: // {"user_123": "Member", "user_456": "Probation", "user_789": "Admin"} // // A player can write to the bulletin board if they're listed in the // guild's "users" map field as a "Member" or "Admin" allow write: if get(/databases/$(database)/documents/guilds/$(guildID)).data.users[(request.auth.uid)] in ["Admin", "Member"]; }
Add Firestore Security Rules with Android Example
In previous tutorial we build a ‘MyThought’ application using Firestore which use for saving thought in Firestore database.In this tutorial we use same project to add firebase security rule.
Add Firebase Authentication
In the Authentication tab of the Firebase console go to the Sign-in Method page and enable ‘Google’
Add Dependency
Add following dependency in your app.gradle file.
dependencies { compile 'com.google.firebase:firebase-firestore:11.4.2' // Other Firebase/Play services deps compile 'com.google.firebase:firebase-auth:11.4.2' compile 'com.google.android.gms:play-services-auth:11.4.2' // FirebaseUI (for authentication) compile 'com.firebaseui:firebase-ui-auth:2.3.0' .... }
Add FirebaseUI
FirebaseUI has separate modules for using Firebase Realtime Database, Cloud Firestore, Firebase Auth, and Cloud Storage. To get started, see the individual instructions for each module.
@Override protected void onStart() { super.onStart(); if (shouldStartSignIn()) { startSignIn(); return; } if (adapter != null) { adapter.startListening(); } } private boolean shouldStartSignIn() { return (FirebaseAuth.getInstance().getCurrentUser() == null); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RC_SIGN_IN) { if (adapter != null) { adapter.startListening(); } } } private void startSignIn() { // Sign in with FirebaseUI Intent intent = AuthUI.getInstance().createSignInIntentBuilder() .setAvailableProviders(Collections.singletonList( new AuthUI.IdpConfig.Builder(AuthUI.GOOGLE_PROVIDER).build())) .setIsSmartLockEnabled(false) .build(); startActivityForResult(intent, RC_SIGN_IN); }
Security Rules
Add the following security rules to your project in the: rules tab:
- Rule 1: Only authorized users can create, read them.
- Rule 2: Only the user who Creates the Thought can delete it. Thought can never be updated.
service cloud.firestore { match /databases/{database}/documents { match /thought/{anything=**}{ allow read: if request.auth.uid != null; allow create: if request.auth.uid != null; allow delete: if resource.data.userId == request.auth.uid; } } }