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’ Firebase Enable Login With 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;
    }
  }
}

Download this project from GitHub

Related Post

Firestore Document Database Data Model.

Firebase Firestore Database for Android Application.