8
votes

I store data in a Cloud Firestore database. Users in my app don´t need to create an account to get data and they can also write data without to login.

Google reminds me every few days that my database is insecure and can be abused by anyone. How can I improve it without accessing Auth variables?

My firebase rules

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write;
    }
  }
}

Is there a way to make my database more secure without using authentication?

The logic of my app:

My database contains surnames and their origin. If someone enters a name, he gets the origin back from the database. Example: "Doe" -> "Mexican". If the last name does not exist in my database, I call an API and save the value to my database. Every user needs both read and write permission.

What can I do here?

3
Any chance, you were able to resolve this thing?Karan_Rana

3 Answers

6
votes

Since the operation that you require writes for is limited (only inserting new items) you have some options:

  • You could deny writes to end user clients, and instead send a request to a cloud function that does exactly the operation you need (after verifying the input, or any other checks you might want, rate limiting, etc). Cloud functions ignore the security rules as they run with administrative access.

Here is a sample node function that performs a write to a realtime database, and it succeeds when both read and write are false in the security rules (your associated package.json obviously needs to depend on firebase-admin and firebase-functions):

const functions = require('firebase-functions');

// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp();

let db = admin.firestore();

// This pushes the "text" parameter into the RDB path /messages/(hash)/original
exports.addMessage = functions.https.onRequest(async (req, res) => {
    // Grab the text parameter.
    const original = req.query.text;
    // Push the new message into the Realtime Database using the Firebase Admin SDK.
    const snapshot = await admin.database().ref('/messages').push({original: original});
    // Respond to the user (could also be a redirect).
    res.send('got it: ' + snapshot.ref.toString());
  });

You may want to read about how the firebase admin SDK does access control but within a cloud function you should have admin rights by default.

  • Using the rules language you could only allow create operations. This removes the ability of the client to update or delete existing data. This isn't quite as secure as the prior method, but might be ok for you:
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read;
      allow create;
    }
  }
}

Also, note this works for firestore (which you are using) but not for realtime database.

Obviously both of these methods could be in some way abused to write lots of data into your database, though the former gives you a lot more control about what is allowed (e.g. you could prevent more than N entries, or more than Y bytes per entry). The later still lets anyone create whatever they want.

4
votes

The first thing is to start with the documentation. It's strongly recommended that you have an understanding of what rules can do, and translate that into requirements for your app.

What you're describing for your app right now is too vague to come up with good rules. To be honest, without Firebase Authentication, it's not possible to accept writes to a database without Authentication and also avoid abuse, since anyone could write anything from anywhere on the internet. This could also cost you large amounts of money if someone discovers your "open" database.

2
votes

Check out this documentation. https://firebase.google.com/docs/firestore/security/rules-structure. Configure the writing of unauthenticated users only in the collections you specify.

service cloud.firestore {
  match /databases/{database}/documents {

    // authentication required
    function issignedin() {
      return request.auth != null;
    }

    // authentication not required
    function notAuthenticated() {
      return request.auth == null;
    }

    // A read rule can be divided into get and list rules
    match /cities/{city} {
      // Applies to single document read requests
      allow get: if notAuthenticated();

      // Applies to queries and collection read requests
      allow list: if notAuthenticated();
    }

    // A write rule can be divided into create, update, and delete rules
    match /cities/{city} {
      // Applies to writes to nonexistent documents
      allow create: if notAuthenticated();

      // Applies to writes to existing documents
      allow update: if notAuthenticated();

      // Applies to delete operations
      allow delete: if notAuthenticated();
    }
  }
}

as a consideration, this will be insecure if the calling API allows indiscriminate writing. Note: If the API you are referring to is the only one you can write, you must configure only the reading as public

service cloud.firestore {
  match /databases/{database}/documents {

    // authentication required
    function issignedin() {
      return request.auth != null;
    }

    // authentication not required
    function notAuthenticated() {
      return request.auth == null;
    }

    // A read rule can be divided into get and list rules
    match /cities/{city} {
      // Applies to single document read requests
      allow get: if notAuthenticated();

      // Applies to queries and collection read requests
      allow list: if notAuthenticated();
    }

    // A write rule can be divided into create, update, and delete rules
    match /cities/{city} {
      // Applies to writes to nonexistent documents
      allow create: if issignedin();

      // Applies to writes to existing documents
      allow update: if issignedin();

      // Applies to delete operations
      allow delete: if issignedin();
    }
  }
}