Firestore security rules to allow access only to specific queries and not the whole collection

Viewed 374

Given the following simplified Firebase Firestore database structure:

  users
    user1
      email: "test1@test.com"
    user2
      email: "test2@test.com"

I want to be able to query if a user with a specific email exists in the database WITHOUT giving access to the whole users collection

Is it possible to achieve this using the database rules without modifying the database structure?

If it's not possible, what would be the best workaround for this?

I see two possible solutions, but it seems to me that they add too much complexity:

  1. Expose that specific query via an API endpoint (maybe using Firebase Functions)
  2. Modify the DB structure as suggested in this thread: Firestore security rules based on request query value

Which approach do you think is better?

1 Answers

To meet the requirement, "query if a user with a specific email exists in the database WITHOUT giving access to the whole users collection," you'll need to rethink your database architecture. Firestore won't allow you to make queries on data that you don't also have read access to.

I would recommend creating a separate collection that just contains email addresses in use like this:

{
  "emails": {
    "jane@example.com": { userId: "abc123" },
    "sally@example.com": { userId: "xyz987" },
    "joe@example.com": { userId: "lmn456" }
  }
}

This collection should be updated everytime a user is added or an email address is changed.

Then set up your Firestore rules like this:

service cloud.firestore {
  match /databases/{database}/documents {
    match /emails/{email} {
      // Allow world-readable access if the email is guessed
      allow get: if true;
      // Prevent anyone from getting a list of emails
      allow list: if false;
    }
  }
}

With all of that you can then securely allow for anonymous queries to check if an email exists without opening your kimono, so to speak.

List All Emails

    firebase.firestore().collection('emails').get()
    .then((results) => console.error("Email listing succeeded!"))
    .catch((error) => console.log("Permission denied, your emails are safe."));

Result: "Permission denied, your emails are safe."

Check if joe@example.com exists

    firebase.firestore().collection('emails').doc('joe@example.com').get()
      .then((node) => console.log({id: node.id, ...node.data()}))
      .catch((error) => console.error(error));

Result: {"id": "joe@example.com": userId: "lmn456"}

Check if sam@example.com exists

    firebase.firestore().collection('emails').doc('sam@example.com').get()
      .then((node) => console.log("sam@example.com exists!"))
      .catch((error) => console.log("sam@example.com not found!"));

Result: sam@example.com not found!

Related