Skip to content
Serverless API Security: Firestore VS Parse
Api Security·

Serverless API Security: Firestore VS Parse

Oron Ben-David
by Oron Ben-David
Serverless API Security: Firestore VS Parse

As you’re building your app, you might want to limit and control access to your stored data. I’m going to present some basic authorization methods using Firestore and Parse.

As a Software Architect at Loadmll.io, I love to simplify “complex” and make them developer-friendly.

Let’s start with a brief description of each service:

Firestore

Cloud Firestore is a flexible, scalable database for mobile, web, and server development from Firebase and Google Cloud Platform. It keeps your data in-sync across client apps using real-time listeners and offers offline support for mobile and web. Full docs here.

Parse

Parse Server is an open-source version of the Parse backend that can be deployed to any infrastructure that can run Node.js. Parse Server lets you use MongoDB or Postgres as a database. Full docs here.

Both services provide developers a built-in security framework:

Security Rules on Firebase

Firebase Security Rules allow you to control access to your stored data. The flexible syntax means that you can create rules that match anything you can think of. You can limit all write operations to the entire database to a specific document.

firebase

Service and database declaration

Cloud Firestore Security Rules always begin with the following declaration:

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

The match /databases/{database}/documents declaration specifies that rules should match any Cloud Firestore database in the project.

rules consist of a match statement specifying a document path and an allow expression detailing when reading the specified data is allowed

Testing using Rules-Simulator

Firestore has a really cool simulator which makes it easier to test and validate your security rules.

First example: Read user is allowed

This example uses one rule with three expressions. Creating a user is permitted only to admins. Update and delete operations are allowed to admins or to the requesting user himself, and read is allowed to all “signedIn” users.

dashboard-1

Second example: Unauthorized user deletion

Here you can see that the delete user request failed, as I tried to delete userId=1234 with a non-admin user.

dashboard-2

Accept or Reject Queries

Your security rules can also accept or reject queries based on their constraints. The request.query variable contains the limitoffset, and orderBy properties of a query. For example, your security rules can deny any query that doesn't limit the maximum number of documents retrieved to a certain range:

allow list: if request.query.limit <= 10;

Nesting match statements

Data in Cloud Firestore is organized into collections of documents, and each document may extend the hierarchy through subcollections.

When nesting match statements, the path of the inner match statement is always relative to the path of the outer match statement. The following rulesets are therefore equivalent:

service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      match /posts/{postId} {
        allow read, write: if <condition>;
      }
    }// equivalent to:
    match /users/{userId}/posts/{postId} {
        allow read, write: if <condition>;      
    }}
}

Recursive wildcards

If you want rules to apply to an arbitrarily deep hierarchy, use the recursive wildcard syntax, {name=**}. For example:

service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{document=**} {
      allow read, write: if <condition>;
    }
  }
}

Security on Parse

Parse takes a slightly different approach. When using Parse, you can specify what operations are allowed per class. This lets you restrict the ways in which clients can access or modify your classes. The most common use-case is to limit client from creating new classes on Parse.

parse

Once restricted, classes may only be created from the Data Browser or with a masterKey.

Parse has a lot to offer, but I’m going to focus on 3 Parse classes:

  • ParseACL

  • ParseUser

  • ParseRole

ParseACL — Access Control Lists

A ParseACL is used to control which users and roles can access or modify a particular object. You can grant read and write permissions separately to specific users or groups of users.

Security For ParseUser Objects

The ParseUser class is secured by default. Data stored in a ParseUser can only be modified by that user. By default, the data can still be read by any client.

control-access

Back4App is a backend-as-a-service platform powered by Parse Open Source which you can use to build your app faster, host it and keep full control over your Backend.

Security For Other Objects

We can use a Parse.ACL to limit the read and write access to a specific user:

const Test = Parse.Object.extend("Test"); const privateTest = new Test(); privateTest.set("description", "This test is private!"); privateTest.setACL(new Parse.ACL(Parse.User.current())); privateTest.save();

Permissions can also be granted to a group of users:

const Test = Parse.Object.extend("Test"); const groupTest = new Test(); const groupACL = new Parse.ACL();for (let i = 0; i < users.length; i++) { groupACL.setReadAccess(users[i], true); groupACL.setWriteAccess(users[i], true); }groupTest.setACL(groupACL); groupTest.save();

All of this is great, but what if you’re trying to implement something not so trivial?

Please welcome Roles!

Parse supports a form of Role-based Access Control, which is more sophisticated than simple ACL.

Roles provide a logical way of grouping users with common access privileges to your Parse data. Roles are named objects that contain users and other roles. Any permission granted to a role is implicitly granted to its users.

This feature allows you to limit the access without having to manually grant permission to every resource for each user.

Security for Role Objects

To create a new Parse.Role, you would write:

const roleACL = new Parse.ACL(); roleACL.setPublicReadAccess(true); const role = new Parse.Role("Admin", roleACL); role.save();// By specifying no write privileges for the ACL, we can ensure the role cannot be altered.

You can add users and roles that should inherit your new role’s permissions through the “users” and “roles” relations on Parse.Role:

const role = new Parse.Role("Admin", roleACL); role.getUsers().add(usersToAddToRole); role.getRoles().add(rolesToAddToRole); role.save();

Conclusion and a short summary

I tried to summarize my comparison in a table:

table

Parse provides a wider range of options, by allowing the relationship between objects, users, and their permissions using ACL and roles.

It is also good to know that Parse has more advanced features like using sessionTokenin order to check the fulfillment of the ParseObject’s ACL, or to use amasterKey which can override any ACLs, and it’ll bypass all the security mechanisms.

In addition, Parse allows you to split the cloud code into separate files and elegantly manage your permissions. There is a graphical display for each object, making it very easy for debugging.

Firebase provides the security rules for managing the permissions by direct access to documents. The fact that the permissions can be configured in a way that is nested allows for very readable code writing. The simulator for the UI and the emulator as npm package significantly increases the ability to test the rules and thus has a huge gap on Parse.

Mypersonal recommendation is to thoroughly understand how these security rules work before your app grows to a significant user base. It is challenging to write these rules while your users are already using your app. I ran into several cases where developers were forced to change the structure of the database due to difficulties in writing security rules.

I hope that this article will help you choose the best service for your specific needs.