Security
Headlines
HeadlinesLatestCVEs

Headline

CVE-2023-41058: Parse

Parse Server is an open source backend server. In affected versions the Parse Cloud trigger beforeFind is not invoked in certain conditions of Parse.Query. This can pose a vulnerability for deployments where the beforeFind trigger is used as a security layer to modify the incoming query. The vulnerability has been fixed by refactoring the internal query pipeline for a more concise code structure and implementing a patch to ensure the beforeFind trigger is invoked. This fix was introduced in commit be4c7e23c6 and has been included in releases 6.2.2 and 5.5.5. Users are advised to upgrade. Users unable to upgrade should make use of parse server’s security layers to manage access levels with Class-Level Permissions and Object-Level Access Control that should be used instead of custom security layers in Cloud Code triggers.

CVE
#sql#vulnerability#web#ios#android#mac#windows#apple#google#microsoft#amazon#ubuntu#ddos#apache#redis#nodejs#js#git#java#php#perl#ldap#nginx#samba#aws#oauth#auth#ssh#mongo#postgres#docker#gradle#maven#ssl

Getting Started

Parse Server is an open source backend that can be deployed to any infrastructure that can run Node.js. You can find the source on the GitHub repo.

  • Parse Server uses MongoDB or PostgreSQL as a database.
  • You can deploy and run Parse Server on your own infrastructure.
  • You can develop and test your app locally using Node.

The following guide describes how to set up Parse Server on your personal computer for local development. If you want to be able to access Parse Server from anywhere and make your app accessible publicly, you would want to deploy Parse Server to a cloud service provider like Amazon Web Services, Google Cloud, Microsoft Azure, Heroku or DigitalOcean. These providers vary in set-up complexity, configuration efforts, pricing model and required knowledge to secure your deployment. You can find guides for how to deploy Parse Server for specific providers in Deploying Parse Server section.

⚠️ Before making Parse Server accessible publicly, we strongly recommend to review all of your Parse Server configuration and read our best practice guide. Failing to properly adapt your Parse Server configuration for a publicly accessible environment may make your deployment vulnerable to malicious intrusions, data leaks and unexpected cost increases.

Prerequisites

  • Node
  • MongoDB (to use Parse Server with MongoDB)
  • PostgreSQL (to use Parse Server with PostgreSQL)

Ensure that the Node.js version is compatible with your version of Parse Server, for details see the compatibility table.

The fastest and easiest way to get started is to run MongoDB and Parse Server locally. Use the bootstrap script to set up Parse Server in the current directory.

sh <(curl -fsSL https://raw.githubusercontent.com/parse-community/parse-server/master/bootstrap.sh)
npm install -g mongodb-runner
mongodb-runner start
npm start

You can use any arbitrary string as your application id and master key. These will be used by your clients to authenticate with the Parse Server.

That’s it! You are now running a standalone version of Parse Server on your machine.

Saving your first object

Now that you’re running Parse Server, it is time to save your first object. We’ll use the REST API, but you can easily do the same using any of the Parse SDKs. Run the following:

curl -X POST \
-H "X-Parse-Application-Id: APPLICATION_ID" \
-H "Content-Type: application/json" \
-d '{"score":123,"playerName":"Sean Plott","cheatMode":false}' \
http://localhost:1337/parse/classes/GameScore

You should get a response similar to this:

{
  "objectId": "2ntvSpRGIK",
  "createdAt": "2022-01-01T12:23:45.678Z"
}

You can now retrieve this object directly (make sure to replace 2ntvSpRGIK with the actual objectId you received when the object was created):

curl -X GET \
  -H "X-Parse-Application-Id: APPLICATION_ID" \
  http://localhost:1337/parse/classes/GameScore/2ntvSpRGIK


// Response
{
  "objectId": "2ntvSpRGIK",
  "score": 123,
  "playerName": "Sean Plott",
  "cheatMode": false,
  "updatedAt": "2022-01-01T12:23:45.678Z",
  "createdAt": "2022-01-01T12:23:45.678Z"
}

Keeping tracks of individual object ids is not ideal, however. In most cases you will want to run a query over the collection, like so:

curl -X GET \
  -H "X-Parse-Application-Id: APPLICATION_ID" \
  http://localhost:1337/parse/classes/GameScore


// The response will provide all the matching objects within the `results` array:
{
  "results": [
    {
      "objectId": "2ntvSpRGIK",
      "score": 123,
      "playerName": "Sean Plott",
      "cheatMode": false,
      "updatedAt": "2022-01-01T12:23:45.678Z",
      "createdAt": "2022-01-01T12:23:45.678Z"
    }
  ]
}

To learn more about using, saving, and querying objects on Parse Server, check out the documentation for the SDK you will be using in your app.

Connect your app to Parse Server

Parse provides SDKs for all the major platforms. Refer to the rest of the Parse Server guide to learn how to connect your app to Parse Server.

Running Parse Server elsewhere

Once you have a better understanding of how the project works, please refer to the Deploying Parse Server section to learn more about additional ways of running Parse Server.

Database

Parse Server lets you use MongoDB or Postgres as a database.

The prefered database is MongoDB but Postgres is a great option if you’re starting a new project and you expect to have a stable Schema.

MongoDB

If you have not used MongoDB before, we highly recommend familiarizing yourself with it first before proceeding.

If this is your first time setting up a MongoDB instance, we recommend a Database-as-a-Service (DBaaS) like MongoDB Atlas or ObjectRocket which provide fully managed MongoDB instances and can help you scale as needed.

Ensure that the MongoDB version is compatible with your version of Parse Server, for details see the compatibility table

When using MongoDB with your Parse app, you need to manage your indexes yourself. You will also need to size up your database as your data grows.

In order to allow for better scaling of your data layer, it is possible to direct queries to a MongoDB secondary for read operations. See: MongoDB Read Preference.

Postgres

Ensure that the Postgres version is compatible with your version of Parse Server, for details see the compatibility table

PostGIS is required if you plan to use geographic or location features.

The Postgres database adapter will be automatically loaded when you pass a valid Postgres URL, for example: postgres://localhost:5432. The available configuration options through the URL are:

postgres://localhost:5432/db?ssl=boolean&rejectUnauthorized=boolean&ca=/path/to/file&pfx=/path/to/file&cert=/path/to/file&key=/path/to/file&passphrase=string&secureOptions=number&client_encoding=string&application_name=string&fallback_application_name=string&max=number&query_timeout=idleTimeoutMillis=number&poolSize=number&binary=boolean&keepAlive=boolean

When using Postgres with your Parse app, you need to manage your indexes yourself.

Details about the configuration options can be found on pg-promise. Some useful combinations are below:

  • SSL with verification - postgres://localhost:5432/db?ca=/path/to/file
  • SSL with no verification - postgres://localhost:5432/db?ssl=true&rejectUnauthorized=false

Caveats

  • You will need to configure a file adapter in order to store files.

  • Join tables are resolved in memory, there is no performance improvements using Postgres over MongoDB for relations or pointers.

  • Mutating the schema implies running ALTER TABLE, therefore we recommend you setup your schema when your tables are not full.

  • The Postgres URL for Parse Server 4.2.0 and below only supports the following configuration options:

    postgres://localhost:5432/db?ssl=boolean&client_encoding=string&application_name=string&fallback_application_name=string&poolSize=number&binary=boolean&keepAlive=boolean

Usage

Parse Server is meant to be mounted on an Express app. Express is a web framework for Node.js. The fastest way to get started is to clone the Parse Server repo, which at its root contains a sample Express app with the Parse API mounted.

The constructor returns an API object that conforms to an Express Middleware. This object provides the REST endpoints for a Parse app. Create an instance like so:

const api = new ParseServer({
  databaseURI: 'mongodb://your.mongo.uri',
  cloud: './cloud/main.js',
  appId: 'myAppId',
  fileKey: 'myFileKey',
  masterKey: 'mySecretMasterKey',
  push: { ... }, // See the Push wiki page
  filesAdapter: ...,
});

A few of the Parse Server Options are as follows:

  • databaseURI: Connection string for your database.
  • cloud: Path to your app’s Cloud Code.
  • appId: A unique identifier for your app.
  • fileKey: A key that specifies a prefix used for file storage. For migrated apps, this is necessary to provide access to files already hosted on Parse.
  • masterKey: A key that overrides all permissions. Keep this secret.
  • clientKey: The client key for your app. (optional)
  • restAPIKey: The REST API key for your app. (optional)
  • javascriptKey: The JavaScript key for your app. (optional)
  • dotNetKey: The .NET key for your app. (optional)
  • push: An object containing push configuration. See Push
  • filesAdapter: An object that implements the FilesAdapter interface. For example, the S3 files adapter
  • auth: Configure support for 3rd party authentication.
  • maxUploadSize: Maximum file upload size. Make sure your server does not restrict max request body size (e.g. nginx.conf client_max_body_size 100m;)

Next, Parse Server can be started with the .start method. This ensures that the database connection is establised, cloud code is registered, and any other startup actions.

In order to access an express middleware for app.use, you can all .app value. This will mount the Parse API at a specified path in your Express app:

const express = require('express');
const ParseServer = require('parse-server').ParseServer;

const app = express();
const api = new ParseServer({ ... });
await api.start();

// Serve the Parse API at /parse URL prefix
app.use('/parse', api.app);

const port = 1337;
app.listen(port, function() {
  console.log('parse-server-example running on port ' + port + '.');
});

And with that, you will have a Parse Server running on port 1337, serving the Parse API at /parse.

Keys

Parse Server does not require the use of client-side keys. This includes the client key, JavaScript key, .NET key, and REST API key. The Application ID is sufficient to secure your app.

However, you have the option to specify any of these four keys upon initialization. Upon doing so, Parse Server will enforce that any clients passing a key matches. The behavior is consistent with hosted Parse.

Read-Only masterKey

Starting parse-server 2.6.5, it is possible to specify a readOnlyMasterKey. When using this key instead of the masterKey, the server will perform all read operations as if they were executing with the masterKey but will fail to execute any write operation.

This key is especially powerful when used with parse-dashboard. Please refer to Parse Dashboard’s documentation for more information.

Using Parse SDKs with Parse Server

To use a Parse SDK with Parse Server, change the server URL to your Parse API URL. For example, if you have Parse Server running locally mounted at /parse:

iOS / OS X / watchOS / tvOS

Swift

let configuration = ParseClientConfiguration {
    $0.applicationId = "YOUR_APP_ID"
    $0.clientKey = ""
    $0.server = "http://localhost:1337/parse"
}
Parse.initialize(with: configuration)

Objective-C

[Parse initializeWithConfiguration:[ParseClientConfiguration configurationWithBlock:^(id<ParseMutableClientConfiguration> configuration) {
   configuration.applicationId = @"YOUR_APP_ID";
   configuration.clientKey = @"";
   configuration.server = @"http://localhost:1337/parse";
}]];

Android

Parse.initialize(new Parse.Configuration.Builder(myContext)
    .applicationId("YOUR_APP_ID")
    .server("http://localhost:1337/parse/")
    ...
    .build()
);

JavaScript

Parse.initialize("YOUR_APP_ID");
Parse.serverURL = 'http://localhost:1337/parse'

.NET

ParseClient.initialize(new ParseClient.Configuration {
    ApplicationId = "YOUR_APP_ID",
    Server = "http://localhost:1337/parse/"
});

PHP

ParseClient::initialize('YOUR_APP_ID', 'YOUR_CLIENT_KEY', 'YOUR_MASTER_KEY');
ParseClient::setServerURL('http://localhost:1337', 'parse'); // server url & mount path passed separately

Deploying Parse Server

The fastest and easiest way to start using Parse Server is to run MongoDB and Parse Server locally. Once you have a better understanding of how the project works, read on to learn how to deploy Parse Server to major infrastructure providers. If your provider is not listed here, please take a look at the list of articles from the community as someone may have already written a guide for it.

Deploying to Heroku and MongoDB Atlas

Heroku and MongoDB Atlas provide an easy way to deploy Parse Server, especially if you’re new to managing your own backend infrastructure.

Here are the steps:

  1. Create a repo for your Express app with the Parse Server middleware mounted (you can use our sample project, or start your own).
  2. Create a Heroku account (if you don’t have one already) and use the Heroku Toolbelt to log in and prepare a new app in the same directory as your Express app. Take a look at Heroku’s Getting Started with Node.js guide for more details.
  3. Set up your MongoDB database:
    1. Sign up for a MongoDB Atlas account.
    2. Create a New Project.
    3. Open the page Database Access and create a new database user with username and password. Remember these user credentials, you will need them later to connect Parse Server to the database. As user privileges choose Read and write to any database, you can change these privileges later on and make them more restrictive according to your needs.
    4. Open the page Clusters and create a new cluster.
    5. On the cluster details page, click on the tab Collections and create a new database.
    6. On the cluster details page, click on the tab Command Line Tools, click on Connect Instructions and choose Connect your application.
    7. Copy the database connection string. Replace the placeholders in the connection string with the username and password of the user you created earlier and the database name.
  4. Use heroku config and note the URI provided by Atlas under the var MONGOLAB_URI
  5. Copy this URI and set it as a new config variable: heroku config:set DATABASE_URI=mongodb://…
  6. Deploy it: git push heroku master

You may also refer to the Heroku Dev Center article on Deploying a Parse Server to Heroku.

Deploying to Glitch and mLab

Before you start, you’ll need:

  • mLab account (for free MongoDB)

Step 1: Creating your database on mLab

mLab provides a Database-as-a-Service for MongoDB. They include a free tier for small sandbox databases. Create an account on mLab and then use the Single-node, Sandbox plan to get a (free) database up and running. Within the mLab wizard, you’ll need to be sure to create a user that has access to connect to the new database. Upon completion, you should be able to construct a Mongo DB connection string like the following:

Step 2: Running parse-server-example on Glitch

Glitch provides an easy way to instantly create and deploy Node.js applications for free. We will use it to run the parse-server-example application.

To get the example server up and running for quick testing, you can simply click the button below:

Now that the import is complete, we’ll need to make two small changes to the 🗝️.env file, which stores private environment variables.

It should look like the following:

# Environment Config

# store your secrets and config variables in here
# only invited collaborators will be able to see your .env values

# reference these in your code with process.env.SECRET

SECRET=
MADE_WITH=

# note: .env is a shell file so there can't be spaces around =
APP_ID=myAppId
MASTER_KEY=your_master_key_here
DATABASE_URI=your_mlab_database_uri_here
SERVER_URL=https://project-name.glitch.me/parse
PARSE_SERVER_LOGS=/tmp

First, change the DATABASE_URI value to your mLab connection string from step 1.

Next, change the project-name portion of the SERVER_URL value to the name of the project that was created. So, if clicking the button creates electric-dinner.glitch.me, your SERVER_URL value would be https://electric-dinner.glitch.me/parse.

You can delete the SECRET and MADE_WITH lines, but there’s no harm in leaving them there.

It is important, for this tutorial, to leave the APP_ID as myAppId as the “test” page hard-codes that and expects that value.

If you’d like to keep this project, create an account on Glitch. Projects created as an anonymous user expire after five days. You can read more about the technical restrictions on free Glitch projects here.

Step 3: Testing

Once you’re finished making your changes to your 🗝️.env file, Glitch will automatically build and deploy your application. If you use the Logs feature within Glitch (click on Tools → Logs), you should see this when your app is deployed:

parse-server-example running on port 3000.

You should then be able to use the “Show” button to launch the application in the browser and get to a page that urges you to star the parse-server GitHub repository. To access the test harness page, add a trailing /test to your URL. This should take you to a page that will allow you to exercise a few parts of the Parse Server Javascript SDK and create a dummy collection and record in your MongoDB. If you’re able to complete steps one through three on this test page, Parse Server is up and running. Optionally, you can go back to mLab.com and take a look at the data that was stored by the test harness to get a feel for how Parse Server stores data in MongoDB.

Deploying on Back4App

Back4App provides an easy way to deploy and host your Parse Server Apps.

Here are the steps:

  1. Create a free Back4App Account.
  2. Create a new Parse App.
  3. Go to your App Core Settings Menu and check your App Keys and Database URI.

If you need to migrate your local Parse Server to Back4App you can follow these guidelines.

Deploying on AWS EC2 Ubuntu using PostgreSQL

Here are the steps:

  1. Log into your AWS account or create a new one AWS Account
  2. Go to AWS EC2 Dashboard
  3. Launch Instances
  4. On Application and OS Images (Amazon Machine Image) select Ubuntu
  5. On Instance Type select t2.micro (is ok for testing and small projects which is Free tier eligible)
  6. Create or select an existing key pair. (If you create a new one click Download Key Pair)
  7. Click Launch Instance
  8. On EC2 Dashboard select the new created instance and click Security
  9. On Security Groups, click on the security group
  10. On Inbound Rules tab, click Edit Inbound Rules
  11. Click Add rule and select PostgreSQL from the dropdown menu and Anywhere-IPv4.
  12. Click Save rules
  13. On EC2 Dashboard select the new created instance and click Connect
  14. Click SSH Client and follow the instructions

Once logged into the ec2 instance we perform the following tasks:

Install Libraries and Dependencies

Update the local package manager apt

Install NodeJS

Check the install was ok, you should see the version installed.

Install npm

Install yarn

Install PostgreSQL Server

Install PostgreSQL

sudo apt-get -y install postgresql

Once is installed, create a password for the user postgres

ALTER USER postgres password 'myStrongPassword';

Quit psql typing \q

Exit postgres user typing exit

Navigate to main folder inside postgresql/version/

cd /etc/postgresql/14/main/

We need to edit two files, pg_hba.conf and postgresql.conf

Scroll down the file and Add host, all, all, 0.0.0.0/0, md5, has to be the first line before local, all, postgres, , peer

TYPE

DATABASE

USER

ADDRESS

METHOD

host

all

all

0.0.0.0/0

md5

local

all

postgres

peer

sudo nano postgresql.conf

Search for #listen_addresses=’localhost’, uncomment the line and replace localhost for *

Restart the PostgreSQL server

sudo service postgresql restart

Setup Parse Server

Create a directory

Run the bash script and follow the instructions, the script have some visual issues and the keys generation doesn’t work.

sh <(curl -fsSL https://raw.githubusercontent.com/parse-community/parse-server/master/bootstrap.sh)

After that, we need to setup the configuration file, use your own appId, masterKey and clientKey, use random strings or some generator tool to create secured keys.

This are the basic options of the config.json file, for the full list you can type parse-server --help or refer to the full options document for more details.

{
  "appId": "exampleAppId",
  "masterKey": "exampleMasterKey",
  "clientKey": "exampleClientKey",
  "appName": "MyApp",
  "cloud": "./cloud/main",
  "databaseURI": "postgres://postgres:myStrongPassword@localhost:5432/postgres"
}

Install Parse Server globally

sudo npm install -g parse-server

Start Parse Server using the script command in the config.json

or manually with the nohup command and specifying the configuration file, this option will keep the server running even if you close the terminal

nohup parse-server config.json &

Check if Parse Server is running typing http://<IP_OR_DOMAIN>:1337 in your browser’s address bar, you should see {"error":"unauthorized"}

Setup Parse Dashboard

Install Parse Dashboard globally

sudo npm install -g parse-dashboard

Once installed, you need to configure Parse Dashboard, go to /usr/lib/node_modules/parse-dashboard/Parse-Dashboard/ and edit the file parse-dashboard-config.json

sudo nano -w parse-dashboard-config.json

This is an example of parse-dashboard.config.json.

{
    "apps": [{
        "serverURL": "http://example.com:1337/parse",
        "appId": "exampleAppId",
        "masterKey": "exampleMasterKey",
        "allowInsecureHTTP": "true",
        "appName": "MyApp"
    }],
    "users": [{
        "user": "admin",
        "pass": "password"
    }]
}

Start Parse Dashboard

or with the nohup command and specifying the configuration file, this option will keep the dashboard running even if you close the terminal

nohup parse-dashboard --dev --config parse-dashboard-config.json &

Check if Parse Dashboard is running typing http://<IP_OR_DOMAIN>:4040 in your browser’s address bar, you should see the login form, use the user and pass that you set in the parse-dashboard-config.json file.

Push Notifications

Parse Server provides basic push notification functionality for iOS, macOS, tvOS and Android. With this feature, you can:

  • Target installations by platform
  • Target installations by a ParseQuery
  • Send push notifications to Android devices through Firebase Cloud Messaging (FCM)
  • Send push notifications to iOS, tvOS and macOS devices through Apple Push Notification Service (APNS)
  • Use most of the sending options

However, there are a few caveats:

  • Does not support super high throughput since it does not employ a job queue system
  • Client push is not supported. You can only use masterKey to send push notifications
  • Delivery reports are not supported
  • Scheduled push is not supported

API

We support most of the sending options. Check the detailed doc here. Parse Server supports the following:

  • channels to target installations by channels
  • where to target installations by ParseQuery
  • priority under data for iOS push priority
  • push_type under data for iOS push type
  • alert under data for notification message
  • number badge under data for iOS badge number
  • sound under data for iOS sound
  • content-available under data for iOS background job
  • category under data for iOS category
  • title under data for Android notification title
  • uri under data for Android notification launched URI
  • custom data under data for ios and Android
  • Increment badge under data for iOS and Android badge number

Here is the list of sending options we do not support yet:

  • push_time for scheduled push

Push Notifications Quick Start****1. Prepare APNS and FCM Credentials

You will need to obtain some credentials from FCM and APNS in order to send push notifications.

APNS (iOS)

If you are setting up push notifications on iOS, tvOS or macOS for the first time, we recommend you visit the raywenderlich.com’s Push Notifications tutorial or appcoda.com’s iOS Push tutorial to help you obtain a production Apple Push Certificate. Parse Server supports the PFX (.p12) file exported from Keychain Access. Parse Server also supports the push certificate and key in .pem format. Token-based authentication instead of a certificate is supported as well.

FCM (Android)

To get your FCM API key, go to the Firebase console and navigate to the project. Navigate to the settings of the project, and within the “Cloud Messaging” tab, you will find it, labeled “Server key”

2. Configure Parse Server

When initializing Parse Server, you should pass an additional push configuration. For example

var server = new ParseServer({
  databaseURI: '...',
  cloud: '...',
  appId: '...',
  masterKey: '...',
  push: {
    android: {
      apiKey: '...'
    },
    ios: {
      pfx: '/file/path/to/XXX.p12',
      passphrase: '', // optional password to your p12/PFX
      bundleId: '',
      production: false
    }
  }
});

The configuration format is

push: {
  android: {
    apiKey: '' // The Server API Key of FCM
  },
  ios: {
    pfx: '', // The filename of private key and certificate in PFX or PKCS12 format from disk  
    passphrase: '', // optional password to your p12
    cert: '', // If not using the .p12 format, the path to the certificate PEM to load from disk
    key: '', // If not using the .p12 format, the path to the private key PEM to load from disk
    bundleId: '', // The bundle identifier associated with your app
    production: false // Specifies which APNS environment to connect to: Production (if true) or Sandbox
  }
}

For iOS, if you would like to use token-based authentication instead of certificates, you should use the following configuration format

push: {
  ios: {
    token: {
      key: '/file/path/to/AuthKey_XXXXXXXXXX.p8',
      keyId: "XXXXXXXXXX",
      teamId: "YYYYYYYYYY" // The Team ID for your developer account
    },
    topic: 'com.domain.appname', // The bundle identifier associated with your app
    production: false
  }
}

If you would like to support both the dev and prod certificates, you can provide an array of configurations like

push: {
  ios: [
    {
      pfx: '', // Dev PFX or P12
      bundleId: '',
      production: false // Dev
    },
    {
      pfx: '', // Prod PFX or P12
      bundleId: '',  
      production: true // Prod
    }
  ],
  tvos: [
    // ...
  ],
  osx: [
    // ...
  ]
}

The configuration for macOS and tvOS works exactly as for iOS. Just add an additional configuration for each platform under the appropriate key. Please note the key for macOS is osx and for tvOS is tvos. If you need to support both the dev and prod certificates, you can do that for all Apple platforms like described above.

var server = new ParseServer({
  databaseURI: '...',
  cloud: '...',
  appId: '...',
  masterKey: '...',
  push: {
    android: {
      apiKey: '...'
    },
    ios: {
      pfx: '/file/path/to/XXX.p12',
      passphrase: '', // optional password to your p12/PFX
      bundleId: '',
      production: false
    },
    osx: {
      pfx: '/file/path/to/XXX.p12',
      passphrase: '', // optional password to your p12/PFX
      bundleId: '',
      production: false
    },
    tvos: {
      pfx: '/file/path/to/XXX.p12',
      passphrase: '', // optional password to your p12/PFX
      bundleId: '',
      production: false
    }
  }
});

If you have a list of certificates, Parse Server’s strategy on choosing them is trying to match installations’ appIdentifier with bundleId first. If it can find some valid certificates, it will use those certificates to establish the connection to APNS and send notifications. If it can not find, it will try to send the notifications with all certificates. Prod certificates first, then dev certificates.

3. Configure Client Apps

Configure an app which connects to Parse Server. We have provided a detailed list of steps to configure your iOS and Android clients.

4. Send Push Notifications

Currently Parse Server only supports sending push notifications by your masterKey. The easiest way to do that is to curl:

curl -X POST \
  -H "X-Parse-Application-Id: you_app_id" \
  -H "X-Parse-Master-Key: your_master_key" \
  -H "Content-Type: application/json" \
  -d '{
        "where": {
          "deviceType": {
            "$in": [
              "ios",
              "android"
            ]
          }
        },
        "data": {
          "title": "The Shining",
          "alert": "All work and no play makes Jack a dull boy."
        }
      }'\   http://your_server_address/parse/push

Push notifications can also be sent from cloud code:

// With promises
Parse.Push.send({
  where: { ... },
  data: { ... }
}, { useMasterKey: true })
.then(function() {
  // Push sent!
}, function(error) {
  // There was a problem :(
});

// With Legacy Backbone callbacks
Parse.Push.send({
  where: query,
  data: {
    alert: 'Test',
    badge: 1,
    sound: 'default'
  }
}, {
  useMasterKey: true,
  success: function() {
    // Push sent!
  },
  error: function(error) {
    // There was a problem :(
  }
});

After sending this to your Parse Server, you should see the push notifications show up on your devices.

Note: The iOS simulator cannot receive push notifications. You must run iOS apps on an iOS device.

In your Parse Server logs, you can see something similar to

// FCM request and response
{
  "request": {
    "params": {
      "priority": "normal",
      "data": {
        "time": "2022-01-01T12:23:45.678Z",
        "push_id": "NTDgWw7kp8",
        "data": "{\"alert\":\"All work and no play makes Jack a dull boy.\"}"
      }
    }
  },
  "response": {
    "multicast_id": 5318039027588186000,
    "success": 1,
    "failure": 0,
    "canonical_ids": 0,
    "results": [
      {
        "registration_id": "APA91bEdLpZnXT76vpkvkD7uWXEAgfrZgkiH_ybkzXqhaNcRw1KHOY0s9GUKNgneGxe2PqJ5Swk1-Vf852kpHAP0Mhoj5wd1MVXpRsRr_3KTQo_dkNd_5wcQ__yWnWLxbeM3kg_JziJK",
        "message_id": "0:1455074519347821%df0f8ea7f9fd7ecd"
      }
    ]
  }
}


APNS Connected
APNS Notification transmitted to:7a7d2864598e1f65e6e02135245b7daf8ea510514e6376f072dc29d53facaa41

These logs mean that the FCM and APNS connections are working.

Push Adapter

Parse Server provides a PushAdapter which abstracts the way we actually send push notifications. The default implementation is ParsePushAdapter, which uses FCM for Android push and APNS for iOS push. However, if you want to use other push providers, you can implement your own PushAdapter. Your adapter needs to implement send(data, installations), which is used for sending data to the installations. You can use ParsePushAdapter as a reference. After you implement your PushAdapter, you can pass that instance to Parse Server like this

var server = new ParseServer({
  databaseURI: '...',
  cloud: '...',
  appId: '...',
  masterKey: '...',
  push: {
    adapter: your_adapter
  }
});

By doing this, after Parse Server decodes the push API request and runs the installation query, your PushAdapter’s send(data, installations) will be called and is responsible for sending the notifications. If you provide your custom PushAdapter, the default ParsePushAdapter will be ignored.

Future Improvements

The current solution provides a good starting point for push notifications. We have a lot of ideas to improve the feature:

  • Support more platforms
  • Support more sending options
  • Support more push providers
  • Support scheduled pushes
  • Support delivery report and error handling
  • Support job queue and benchmarking

If you’re interested in any of these features, don’t hesitate to jump in and send a PR to the repo. We would love to work with you!

Notes****Silent Notifications

If you are seeing situations where silent notifications are failing to deliver, please ensure that your payload is setting the content-available attribute to Int(1) (or just 1 as in javascript) and not “1”. This value will be explicitly checked.

When sending a push notification to APNs you also have to set push_type to background for delivering silent notifications to devices running iOS 13 and later, or watchOS 6 or later.

PPNS

  • PPNS Protocol Specification (for Parse IoT devices)

Configuring your clients to receive Push Notifications

The following will guide you through the necessary steps to configure your iOS and Android client apps to receive push notifications from Parse Server. If you haven’t yet, you will first need to prepare your APNS and FCM credentials as documented in Step 1 of the Push Notifications Quick Start.

iOS Apps****Register Device for Push Notifications

Open up your AppDelegate.swift, AppDelegate.m, or AppDelegate.cs file and make your app register for remote notifications by adding the following in your application:didFinishLaunchingWithOptions: function:

// Swift
let types: UIUserNotificationType = [.Alert, .Badge, .Sound]
let settings = UIUserNotificationSettings(forTypes: types, categories: nil)
application.registerUserNotificationSettings(settings)
application.registerForRemoteNotifications()


// Objective-C
UIUserNotificationType userNotificationTypes = (UIUserNotificationTypeAlert |
UIUserNotificationTypeBadge |
UIUserNotificationTypeSound);
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:userNotificationTypes
categories:nil];
[application registerUserNotificationSettings:settings];
[application registerForRemoteNotifications];


// Xamarin
UIUserNotificationType notificationTypes = (UIUserNotificationType.Alert |
UIUserNotificationType.Badge |
UIUserNotificationType.Sound);
var settings = UIUserNotificationSettings.GetSettingsForTypes(notificationTypes,
new NSSet(new string[] { }));
UIApplication.SharedApplication.RegisterUserNotificationSettings(settings);
UIApplication.SharedApplication.RegisterForRemoteNotifications();

// Handle Push Notifications
ParsePush.ParsePushNotificationReceived += (object sender, ParsePushNotificationEventArgs args) => {
  // Process Push Notification payload here.
};

Store the device token and handle the UI for notifications by adding the following to your main app delegate:

// Swift
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
    let installation = PFInstallation.currentInstallation()
    installation.setDeviceTokenFromData(deviceToken)
    installation.saveInBackground()
}

func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
    if error.code == 3010 {
        print("Push notifications are not supported in the iOS Simulator.")
    } else {
        print("application:didFailToRegisterForRemoteNotificationsWithError: %@", error)
    }
}

func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
    PFPush.handlePush(userInfo)
}


// Objective-C
UIUserNotificationType userNotificationTypes = (UIUserNotificationTypeAlert |
                                                UIUserNotificationTypeBadge |
                                                UIUserNotificationTypeSound);
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:userNotificationTypes
                                                                         categories:nil];
[application registerUserNotificationSettings:settings];
[application registerForRemoteNotifications];


// Xamarin
UIUserNotificationType notificationTypes = (UIUserNotificationType.Alert |
                                            UIUserNotificationType.Badge |
                                            UIUserNotificationType.Sound);
var settings = UIUserNotificationSettings.GetSettingsForTypes(notificationTypes,
                                                              new NSSet(new string[] { }));
UIApplication.SharedApplication.RegisterUserNotificationSettings(settings);
UIApplication.SharedApplication.RegisterForRemoteNotifications();

// Handle Push Notifications
ParsePush.ParsePushNotificationReceived += (object sender, ParsePushNotificationEventArgs args) => {
  // Process Push Notification payload here.
};

Compile and run!

If you configured your app correctly, installation objects will automatically be saved to Parse Server when you run your app. You can run this curl command to verify:

curl -X GET \
  -H "X-Parse-Application-Id: YOUR_APP_ID" \
  -H "X-Parse-Master-Key: YOUR_MASTER_KEY" \
  http://your_parse_server:1337/parse/installations

Proceed to Step 4.****Android apps****FCM Push Setup

Add this in your root build.gradle file (not your module build.gradle file):

allprojects {
    repositories {
        ...
        maven { url "https://jitpack.io" }
    }
}

Then, add the library to your project build.gradle

dependencies {
    implementation "com.github.parse-community.Parse-SDK-Android:fcm:latest.version.here"
}

with the latest version being

Then, follow Google’s docs for setting up an Firebase app. Although the steps are different for setting up FCM with Parse, it is also a good idea to read over the Firebase FCM Setup. You will need to do the following:

  • Add app to Firebase console.
  • Add the com.google.gms.google-services Gradle plugin (see setup guide)
  • Download and add google-services.json to your app/ dir.
  • Remove GcmBroadcastReceiver, PushService, com.parse.push.gcm_sender_id if upgrading from GCM.
  • Added ParseFirebaseInstanceIdService and ParseFirebaseMessagingService to your AndroidManifest.xml file (as shown below):

You will need to register some services in your manifest, specifically:

<service
    android:name="com.parse.fcm.ParseFirebaseInstanceIdService"
    android:exported="true">
    <intent-filter>
        <action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
    </intent-filter>
</service>

Additional, you will register:

<service
    android:name="com.parse.fcm.ParseFirebaseMessagingService">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT"/>
    </intent-filter>
</service>

After these services are registered in the Manifest, you then need to register the push broadcast receiver:

<receiver
    android:name="com.parse.ParsePushBroadcastReceiver"
    android:exported="false">
    <intent-filter>
        <action android:name="com.parse.push.intent.RECEIVE" />
        <action android:name="com.parse.push.intent.DELETE" />
        <action android:name="com.parse.push.intent.OPEN" />
    </intent-filter>
</receiver>

Custom Notifications

If you need to customize the notification that is sent out from a push, you can do so by extending ParsePushBroadcastReceiver with your own class and registering it instead in the Manifest.

Register Device for Push Notifications

Create an Installation object by adding the following to the onCreate method of your Application class:

// Native: Application.java
public void onCreate() {
  // ...
  ParseInstallation.getCurrentInstallation().saveInBackground();
}


// Xamarin: Application.cs

// IMPORTANT: Change "parsexamarinpushsample" to match your namespace.
[Application(Name = "parsexamarinpushsample.ParseApplication")]
class ParseApplication : Application {
  // ...

  public override void OnCreate() {
    base.OnCreate();

    // ...

    ParsePush.ParsePushNotificationReceived += ParsePush.DefaultParsePushNotificationReceivedHandler;
  }
}

Compile and run!

If you configured your app correctly, installation objects will automatically be saved to Parse Server when you run your app. You can run this curl command to verify:

curl -X GET \
  -H "X-Parse-Application-Id: YOUR_APP_ID" \
  -H "X-Parse-Master-Key: YOUR_MASTER_KEY" \
  http://your_parse_server:1337/parse/installations

Proceed to Step 4.

Note that GCM push support is deprecated and FCM should be used instead, but instructions for GCM setup can be found here

Class Level Permissions

Class level permissions are a security feature from that allows one to restrict access on a broader way than the ACL based permissions.

requiresAuthentication

If you want to restrict access to a full class to only authenticated users, you can use the requiresAuthentication class level permission. For example, you want to allow your authenticated users to find and get objects from your application and your admin users to have all privileges, you would set the CLP:

// PUT http://localhost:1337/schemas/:className
// Set the X-Parse-Application-Id and X-Parse-Master-Key header
// body:
{
  classLevelPermissions:
  {
    "find": {
      "requiresAuthentication": true,
      "role:admin": true
    },
    "get": {
      "requiresAuthentication": true,
      "role:admin": true
    },
    "create": { "role:admin": true },
    "update": { "role:admin": true },
    "delete": { "role:admin": true },
  }
}

Note that this is in no way securing your content. If you allow anyone to log in to your server, any client will be able to query this object.

Security

As your app development progresses, you will want to use Parse’s security features in order to safeguard data. This document explains the ways in which you can secure your apps.

If your app is compromised, it’s not only you as the developer who suffers, but potentially the users of your app as well. Continue reading for our suggestions for sensible defaults and precautions to take before releasing your app into the wild.

Client vs. Server

When an app first connects to Parse, it identifies itself with an Application ID and a Client key (or REST Key, or .NET Key, or JavaScript Key, depending on which platform you’re using). These are not secret and by themselves they do not secure an app. These keys are shipped as a part of your app, and anyone can decompile your app or proxy network traffic from their device to find your client key. This exploit is even easier with JavaScript — one can simply “view source” in the browser and immediately find your client key.

This is why Parse has many other security features to help you secure your data. The client key is given out to your users, so anything that can be done with just the client key is doable by the general public, even malicious hackers.

The master key, on the other hand, is definitely a security mechanism. Using the master key allows you to bypass all of your app’s security mechanisms, such as class-level permissions and ACLs. Having the master key is like having root access to your app’s servers, and you should guard your master key with the same zeal with which you would guard your production machines’ root password.

The overall philosophy is to limit the power of your clients (using client keys), and to perform any sensitive actions requiring the master key in Cloud Code. You’ll learn how to best wield this power in the section titled Implementing Business Logic in Cloud Code.

A final note: It is recommended to setup HTTPS and SSL in your server, to avoid man-in-the-middle attacks, but Parse works fine as well with non-HTTPS connections.

Class-Level Permissions

The second level of security is at the schema and data level. Enforcing security measures at this level will restrict how and when client applications can access and create data on Parse. When you first begin developing your Parse application, all of the defaults are set so that you can be a more productive developer. For example:

  • A client application can create new classes on Parse
  • A client application can add fields to classes
  • A client application can modify or query for objects on Parse

You can configure any of these permissions to apply to everyone, no one, or to specific users or roles in your app. Roles are groups that contain users or other roles, which you can assign to an object to restrict its use. Any permission granted to a role is also granted to any of its children, whether they are users or other roles, enabling you to create an access hierarchy for your apps. Each of the Parse guides includes a detailed description of employing Roles in your apps.

Once you are confident that you have the right classes and relationships between classes in your app, you should begin to lock it down by doing the following:

Almost every class that you create should have these permissions tweaked to some degree. For classes where every object has the same permissions, class-level settings will be most effective. For example, one common use case entails having a class of static data that can be read by anyone but written by no one.

Restricting class creation

As a start, you can configure your application so that clients cannot create new classes on Parse. This is done by setting the key allowClientClassCreation to false in your ParseServer configuration. See the project Readme for an overview of Configuring your ParseServer. Once restricted, classes may only be created from the Data Browser or with a the masterKey. This will prevent attackers from filling your database with unlimited, arbitrary new classes.

Enforcing Private Users

Requires Parse Server 5.0.0+

By default, Parse Server creates Users with public read access. This allows other users, and un-authenticated users, to read data such as email. When moving to production, set the key enforcePrivateUsers to true, as this will remove the public read access to new users.

Configuring Class-Level Permissions

Parse lets you specify what operations are allowed per class. This lets you restrict the ways in which clients can access or modify your classes. To change these settings, go to the Data Browser, select a class, and click the “Security” button.

You can configure the client’s ability to perform each of the following operations for the selected class:

  • Read:

    • Get: With Get permission, users can fetch objects in this table if they know their objectIds.

    • Find: Anyone with Find permission can query all of the objects in the table, even if they don’t know their objectIds. Any table with public Find permission will be completely readable by the public, unless you put an ACL on each object.

  • Write:

    • Update: Anyone with Update permission can modify the fields of any object in the table that doesn’t have an ACL. For publicly readable data, such as game levels or assets, you should disable this permission.

    • Create: Like Update, anyone with Create permission can create new objects of a class. As with the Update permission, you’ll probably want to turn this off for publicly readable data.

    • Delete: With this permission, people can delete any object in the table that doesn’t have an ACL. All they need is its objectId.

  • Add fields: Parse classes have schemas that are inferred when objects are created. While you’re developing your app, this is great, because you can add a new field to your object without having to make any changes on the backend. But once you ship your app, it’s very rare to need to add new fields to your classes automatically. You should pretty much always turn off this permission for all of your classes when you submit your app to the public.

For each of the above actions, you can grant permission to all users (which is the default), or lock permissions down to a list of roles and users. For example, a class that should be available to all users would be set to read-only by only enabling get and find. A logging class could be set to write-only by only allowing creates. You could enable moderation of user-generated content by providing update and delete access to a particular set of users or roles.

Object-Level Access Control

Once you’ve locked down your schema and class-level permissions, it’s time to think about how data is accessed by your users. Object-level access control enables one user’s data to be kept separate from another’s, because sometimes different objects in a class need to be accessible by different people. For example, a user’s private personal data should be accessible only to them.

Parse also supports the notion of anonymous users for those apps that want to store and protect user-specific data without requiring explicit login.

When a user logs into an app, they initiate a session with Parse. Through this session they can add and modify their own data but are prevented from modifying other users’ data.

Access Control Lists

The easiest way to control who can access which data is through access control lists, commonly known as ACLs. The idea behind an ACL is that each object has a list of users and roles along with what permissions that user or role has. A user needs read permissions (or must belong to a role that has read permissions) in order to retrieve an object’s data, and a user needs write permissions (or must belong to a role that has write permissions) in order to update or delete that object.

Once you have a User, you can start using ACLs. Remember: Users can be created through traditional username/password sign up, through a third-party login system like Facebook or Twitter, or even by using Parse’s automatic anonymous users functionality. To set an ACL on the current user’s data to not be publicly readable, all you have to do is:

Most apps should do this. If you store any sensitive user data, such as email addresses or phone numbers, you need to set an ACL like this so that the user’s private information isn’t visible to other users. If an object doesn’t have an ACL, it’s readable and writeable by everyone. The only exception is the _User class. We never allow users to write each other’s data, but they can read it by default. (If you as the developer need to update other _User objects, remember that your master key can provide the power to do this.)

To make it super easy to create user-private ACLs for every object, we have a way to set a default ACL that will be used for every new object you create:

If you want the user to have some data that is public and some that is private, it’s best to have two separate objects. You can add a pointer to the private data from the public one.

Of course, you can set different read and write permissions on an object. For example, this is how you would create an ACL for a public post by a user, where anyone can read it:

Sometimes it’s inconvenient to manage permissions on a per-user basis, and you want to have groups of users who get treated the same (like a set of admins with special powers). Roles are a special kind of object that let you create a group of users that can all be assigned to the ACL. The best thing about roles is that you can add and remove users from a role without having to update every single object that is restricted to that role. To create an object that is writeable only by admins:

Of course, this snippet assumes you’ve already created a role named “admins”. This is often reasonable when you have a small set of special roles set up while developing your app. Roles can also be created and updated on the fly — for example, adding new friends to a “friendOf___” role after each connection is made.

All this is just the beginning. Applications can enforce all sorts of complex access patterns through ACLs and class-level permissions. For example:

  • For private data, read and write access can be restricted to the owner.
  • For a post on a message board, the author and members of the “Moderators” role can have “write” access, and the general public can have “read” access.
  • For logging data that will only be accessed by the developer through the REST API using the master key, the ACL can deny all permissions.
  • Data created by a privileged group of users or the developer, like a global message of the day, can have public read access but restrict write access to an “Administrators” role.
  • A message sent from one user to another can give “read” and “write” access just to those users.

For the curious, here’s the format for an ACL that restricts read and write permissions to the owner (whose objectId is identified by “aSaMpLeUsErId”) and enables other users to read the object:

{
    "*": { "read":true },
    "aSaMpLeUsErId": { "read" :true, "write": true }
}

And here’s another example of the format of an ACL that uses a Role:

{
    "role:RoleName": { "read": true },
    "aSaMpLeUsErId": { "read": true, "write": true }
}

Pointer Permissions

Pointer permissions are a special type of class-level permission that create a virtual ACL on every object in a class, based on users stored in pointer fields on those objects. For example, given a class with an owner field, setting a read pointer permission on owner will make each object in the class only readable by the user in that object’s owner field. For a class with a sender and a reciever field, a read pointer permission on the receiver field and a read and write pointer permission on the sender field will make each object in the class readable by the user in the sender and receiver field, and writable only by the user in the sender field.

Given that objects often already have pointers to the user(s) that should have permissions on the object, pointer permissions provide a simple and fast solution for securing your app using data which is already there, that doesn’t require writing any client code or cloud code.

Pointer permissions are like virtual ACLs. They don’t appear in the ACL column, but if you are familiar with how ACLs work, you can think of them like ACLs. In the above example with the sender and receiver, each object will act as if it has an ACL of:

{
    "<SENDER_USER_ID>": {
        "read": true,
        "write": true
    },
    "<RECEIVER_USER_ID>": {
        "read": true
    }
}

Note that this ACL is not actually created on each object. Any existing ACLs will not be modified when you add or remove pointer permissions, and any user attempting to interact with an object can only interact with the object if both the virtual ACL created by the pointer permissions, and the real ACL already on the object allow the interaction. For this reason, it can sometimes be confusing to combine pointer permissions and ACLs, so we recommend using pointer permissions for classes that don’t have many ACLs set. Fortunately, it’s easy to remove pointer permissions if you later decide to use Cloud Code or ACLs to secure your app.

Requires Authentication permission (requires parse-server >= 2.3.0)

Starting version 2.3.0, parse-server introduces a new Class Level Permission requiresAuthentication. This CLP prevents any non authenticated user from performing the action protected by the CLP.

For example, you want to allow your authenticated users to find and get Announcement’s from your application and your admin role to have all privileged, you would set the CLP:

// POST http://my-parse-server.com/schemas/Announcement
// Set the X-Parse-Application-Id and X-Parse-Master-Key header
// body:
{
  classLevelPermissions:
  {
    "find": {
      "requiresAuthentication": true,
      "role:admin": true
    },
    "get": {
      "requiresAuthentication": true,
      "role:admin": true
    },
    "create": { "role:admin": true },
    "update": { "role:admin": true },
    "delete": { "role:admin": true }
  }
}

Effects:

  • Non authenticated users won’t be able to do anything.
  • Authenticated users (any user with a valid sessionToken) will be able to read all the objects in that class
  • Users belonging to the admin role, will be able to perform all operations.

:warning: Note that this is in no way securing your content, if you allow anyone to login to your server, every client will still be able to query this object.

CLP and ACL interaction

Class-Level Permissions (CLPs) and Access Control Lists (ACLs) are both powerful tools for securing your app, but they don’t always interact exactly how you might expect. They actually represent two separate layers of security that each request has to pass through to return the correct information or make the intended change. These layers, one at the class level, and one at the object level, are shown below. A request must pass through BOTH layers of checks in order to be authorized. Note that despite acting similarly to ACLs, Pointer Permissions are a type of class level permission, so a request must pass the pointer permission check in order to pass the CLP check.

As you can see, whether a user is authorized to make a request can become complicated when you use both CLPs and ACLs. Let’s look at an example to get a better sense of how CLPs and ACLs can interact. Say we have a Photo class, with an object, photoObject. There are 2 users in our app, user1 and user2. Now lets say we set a Get CLP on the Photo class, disabling public Get, but allowing user1 to perform Get. Now let’s also set an ACL on photoObject to allow Read - which includes GET - for only user2.

You may expect this will allow both user1 and user2 to Get photoObject, but because the CLP layer of authentication and the ACL layer are both in effect at all times, it actually makes it so neither user1 nor user2 can Get photoObject. If user1 tries to Get photoObject, it will get through the CLP layer of authentication, but then will be rejected because it does not pass the ACL layer. In the same way, if user2 tries to Get photoObject, it will also be rejected at the CLP layer of authentication.

Now lets look at example that uses Pointer Permissions. Say we have a Post class, with an object, myPost. There are 2 users in our app, poster, and viewer. Lets say we add a pointer permission that gives anyone in the Creator field of the Post class read and write access to the object, and for the myPost object, poster is the user in that field. There is also an ACL on the object that gives read access to viewer. You may expect that this will allow poster to read and edit myPost, and viewer to read it, but viewer will be rejected by the Pointer Permission, and poster will be rejected by the ACL, so again, neither user will be able to access the object.

Because of the complex interaction between CLPs, Pointer Permissions, and ACLs, we recommend being careful when using them together. Often it can be useful to use CLPs only to disable all permissions for a certain request type, and then using Pointer Permissions or ACLs for other request types. For example, you may want to disable Delete for a Photo class, but then put a Pointer Permission on Photo so the user who created it can edit it, just not delete it. Because of the especially complex way that Pointer Permissions and ACLs interact, we usually recommend only using one of those two types of security mechanisms.

Security Edge Cases

There are some special classes in Parse that don’t follow all of the same security rules as every other class. Not all classes follow Class-Level Permissions (CLPs) or Access Control Lists (ACLs) exactly how they are defined, and here those exceptions are documented. Here “normal behavior” refers to CLPs and ACLs working normally, while any other special behaviors are described in the footnotes.

_User

_Installation

Get

normal behaviour [1, 2, 3]

ignores CLP, but not ACL

Find

normal behavior [3]

master key only [6]

Create

normal behavior [4]

ignores CLP

Update

normal behavior [5]

ignores CLP, but not ACL [7]

Delete

normal behavior [5]

master key only [7]

Add Field

normal behavior

normal behavior

  1. Logging in, or /parse/login in the REST API, does not respect the Get CLP on the user class. Login works just based on username and password, and cannot be disabled using CLPs.

  2. Retrieving the current user, or becoming a User based on a session token, which are both /parse/users/me in the REST API, do not respect the Get CLP on the user class.

  3. Read ACLs do not apply to the logged in user. For example, if all users have ACLs with Read disabled, then doing a find query over users will still return the logged in user. However, if the Find CLP is disabled, then trying to perform a find on users will still return an error.

  4. Create CLPs also apply to signing up. So disabling Create CLPs on the user class also disables people from signing up without the master key.

  5. Users can only Update and Delete themselves. Public CLPs for Update and Delete may still apply. For example, if you disable public Update for the user class, then users cannot edit themselves. But no matter what the write ACL on a user is, that user can still Update or Delete itself, and no other user can Update or Delete that user. As always, however, using the master key allows users to update other users, independent of CLPs or ACLs.

  6. Get requests on installations follow ACLs normally. Find requests without master key is not allowed unless you supply the installationId as a constraint.

  7. Update requests on installations do adhere to the ACL defined on the installation, but Delete requests are master-key-only. For more information about how installations work, check out the installations section of the REST guide.

Data Integrity in Cloud Code

For most apps, care around keys, class-level permissions, and object-level ACLs are all you need to keep your app and your users’ data safe. Sometimes, though, you’ll run into an edge case where they aren’t quite enough. For everything else, there’s Cloud Code.

Cloud Code allows you to upload JavaScript to Parse’s servers, where we will run it for you. Unlike client code running on users’ devices that may have been tampered with, Cloud Code is guaranteed to be the code that you’ve written, so it can be trusted with more responsibility.

One particularly common use case for Cloud Code is preventing invalid data from being stored. For this sort of situation, it’s particularly important that a malicious client not be able to bypass the validation logic.

To create validation functions, Cloud Code allows you to implement a beforeSave trigger for your class. These triggers are run whenever an object is saved, and allow you to modify the object or completely reject a save. For example, this is how you create a Cloud Code beforeSave trigger to make sure every user has an email address set:

Parse.Cloud.beforeSave(Parse.User, request => {
  const user = request.object;
  if (!user.get("email")) {
    throw "Every user must have an email address.";
  }
});

Validations can lock down your app so that only certain values are acceptable. You can also use afterSave validations to normalize your data (e.g. formatting all phone numbers or currency identically). You get to retain most of the productivity benefits of accessing Parse data directly from your client applications, but you can also enforce certain invariants for your data on the fly.

Common scenarios that warrant validation include:

  • Making sure phone numbers have the right format
  • Sanitizing data so that its format is normalized
  • Making sure that an email address looks like a real email address
  • Requiring that every user specifies an age within a particular range
  • Not letting users directly change a calculated field
  • Not letting users delete specific objects unless certain conditions are met

Implementing Business Logic in Cloud Code

While validation often makes sense in Cloud Code, there are likely certain actions that are particularly sensitive, and should be as carefully guarded as possible. In these cases, you can remove permissions or the logic from clients entirely and instead funnel all such operations to Cloud Code functions.

When a Cloud Code function is called, it can use the optional {useMasterKey:true} parameter to gain the ability to modify user data. With the master key, your Cloud Code function can override any ACLs and write data. This means that it’ll bypass all the security mechanisms you’ve put in place in the previous sections.

Say you want to allow a user to “like” a Post object without giving them full write permissions on the object. You can do this by having the client call a Cloud Code function instead of modifying the Post itself:

The master key should be used carefully. setting useMasterKey to true only in the individual API function calls that need that security override:

Parse.Cloud.define("like", async request => {
  var post = new Parse.Object("Post");
  post.id = request.params.postId;
  post.increment("likes");
  await post.save(null, { useMasterKey: true })
});

One very common use case for Cloud Code is sending push notifications to particular users. In general, clients can’t be trusted to send push notifications directly, because they could modify the alert text, or push to people they shouldn’t be able to. Your app’s settings will allow you to set whether “client push” is enabled or not; we recommend that you make sure it’s disabled. Instead, you should write Cloud Code functions that validate the data to be pushed and sent before sending a push.

Rate Limiting

  • Available on Parse Server >=6.0.0 *

It’s important to restrict how often a client can call the Parse Server API. This prevents malicious attacks that could:

  • overwhelm server resources by exceeding expected API traffic
  • collect large amounts of data (“data scraping”)
  • repeatedly guess passwords, object IDs, installation IDs or other data (“brute force”)

Parse Sever offers a mechanism to enforce rate limits by setting the Parse Server option rateLimit, or by specifying a rateLimit object on a Cloud Function validator.

The valid options for a rate limit are:

  • requestPath: The path of the API route to be rate limited.
  • requestMethods: Optional, the HTTP request methods to be rate limited.
  • requestTimeWindow: The window of time in milliseconds within which the number of requests set in requestCount can be made before the rate limit is applied.
  • requestCount: The number of requests that can be made per IP address within the time window set in requestTimeWindow before the rate limit is applied.
  • errorResponseMessage: The error message that should be returned in the body of the HTTP 429 response when the rate limit is hit. Default is Too many requests…
  • includeInternalRequests: Optional, whether the rate limit will also apply to requests that are made in by Cloud Code.
  • includeMasterKey: Optional, whether the rate limit will also apply to requests using the masterKey
  • redisUrl Optional, the URL of the Redis server to store rate limit data.

To specify a server-wide rate limit of 200 requests per 15 minute window:

const parseServer = new ParseServer({
  rateLimit: {
    requestPath: '*',
    requestTimeWindow: 15 * 60 * 1000,
    requestCount: 200,
  },
});

To specify a cloud function specific rate limit of 3 request per hour:

Parse.Cloud.define('someFunction', () => {
  return 'Hello world';
}, {
  rateLimit: {
    requestTimeWindow: 60 * 60 * 1000,
    requestCount: 3,
  }
});

Rate limits can also be applied to beforeSave triggers to restrict how often a given class is written to:

Parse.Cloud.beforeSave('TestObject', () => {}, {
  rateLimit: {
    requestTimeWindow: 1 * 60 * 1000 // one write per minute,,
    requestCount: 1,
    errorResponseMessage: 'Too many requests!',
  },
});

⚠️ Rate limits should be enforced as far away from Parse Server as possible to mitigate possible impacts on resource costs, availability and integrity. While Parse Server offers a rate limiting mechanism as a conveniently available security feature without requiring a deep level of expertise, it is not considered best practice to enforce rate limits only after requests already reached the server. For better protection we advice to examine your network architecture an consider enforcing rate limits on the outer edge of the cloud if using a content delivery network, or at least before requests reach the server resource. Consult your cloud service provider for recommended rate limit and firewall solutions for your resources.

Parse Security Summary

Parse provides a number of ways for you to secure data in your app. As you build your app and evaluate the kinds of data you will be storing, you can make the decision about which implementation to choose.

It is worth repeating that that the Parse User object is readable by all other users by default. You will want to set the ACL on your User object accordingly if you wish to prevent data contained in the User object (for example, the user’s email address) from being visible by other users.

Most classes in your app will fall into one of a couple of easy-to-secure categories. For fully public data, you can use class-level permissions to lock down the table to put publicly readable and writeable by no one. For fully private data, you can use ACLs to make sure that only the user who owns the data can read it. But occasionally, you’ll run into situations where you don’t want data that’s fully public or fully private. For example, you may have a social app, where you have data for a user that should be readable only to friends whom they’ve approved. For this you’ll need to a combination of the techniques discussed in this guide to enable exactly the sharing rules you desire.

We hope that you’ll use these tools to do everything you can to keep your app’s data and your users’ data secure. Together, we can make the web a safer place.

Performance

As your app scales, you will want to ensure that it performs well under increased load and usage. This document provides guidelines on how you can optimize your app’s performance. While you can use Parse Server for quick prototyping and not worry about performance, you will want to keep our performance guidelines in mind when you’re initially designing your app. We strongly advise that you make sure you’ve followed all suggestions before releasing your app.

You can improve your app’s performance by looking at the following:

  • Writing efficient queries.
  • Writing restrictive queries.
  • Using client-side caching.
  • Using Cloud Code.
  • Avoiding count queries.
  • Using efficient search techniques.

Keep in mind that not all suggestions may apply to your app. Let’s look into each one of these in more detail.

Write Efficient Queries

Parse objects are stored in a database. A Parse query retrieves objects that you are interested in based on conditions you apply to the query. To avoid looking through all the data present in a particular Parse class for every query, the database can use an index. An index is a sorted list of items matching a given criteria. Indexes help because they allow the database to do an efficient search and return matching results without looking at all of the data. Indexes are typically smaller in size and available in memory, resulting in faster lookups.

Indexing

You are responsible for managing your database and maintaining indexes when using Parse Server. If your data is not indexed, every query will have to go through the the entire data for a class to return a query result. On the other hand, if your data is indexed appropriately, the number of documents scanned to return a correct query result should be low.

The order of a query constraint’s usefulness is:

  • Equal to
  • Contained In
  • Less than, Less than or Equal to, Greater than, Greater than or Equal to
  • Prefix string matches
  • Not equal to
  • Not contained in
  • Everything else

Take a look at the following query to retrieve GameScore objects:

Creating an index query based on the score field would yield a smaller search space in general than creating one on the playerName field.

When examining data types, booleans have a very low entropy and and do not make good indexes. Take the following query constraint:

The two possible values for “cheatMode” are true and false. If an index was added on this field it would be of little use because it’s likely that 50% of the records will have to be looked at to return query results.

Data types are ranked by their expected entropy of the value space for the key:

  • GeoPoints
  • Array
  • Pointer
  • Date
  • String
  • Number
  • Other

Even the best indexing strategy can be defeated by suboptimal queries.

Efficient Query Design

Writing efficient queries means taking full advantage of indexes. Let’s take a look at some query constraints that negate the use of indexes:

  • Not Equal To
  • Not Contained In

Additionally, the following queries under certain scenarios may result in slow query responses if they can’t take advantage of indexes:

  • Regular Expressions
  • Ordered By

Not Equal To

For example, let’s say you’re tracking high scores for a game in a GameScore class. Now say you want to retrieve the scores for all players except a certain one. You could create this query:

This query can’t take advantage of indexes. The database has to look at all the objects in the “GameScore” class to satisfy the constraint and retrieve the results. As the number of entries in the class grows, the query takes longer to run.

Luckily, most of the time a “Not Equal To” query condition can be rewritten as a “Contained In” condition. Instead of querying for the absence of values, you ask for values which match the rest of the column values. Doing this allows the database to use an index and your queries will be faster.

For example if the User class has a column called state which has values “SignedUp”, “Verified”, and “Invited”, the slow way to find all users who have used the app at least once would be to run the query:

It would be faster to use the “Contained In” condition when setting up the query:

Sometimes, you may have to completely rewrite your query. Going back to the “GameScore” example, let’s say we were running that query to display players who had scored higher than the given player. We could do this differently, by first getting the given player’s high score and then using the following query:

The new query you use depends on your use case. This may sometimes mean a redesign of your data model.

Not Contained In

Similar to “Not Equal To”, the “Not Contained In” query constraint can’t use an index. You should try and use the complementary “Contained In” constraint. Building on the User example, if the state column had one more value, “Blocked”, to represent blocked users, a slow query to find active users would be:

Using a complimentary “Contained In” query constraint will always be faster:

This means rewriting your queries accordingly. Your query rewrites will depend on your schema set up. It may mean redoing that schema.

Regular Expressions

Regular expression queries should be avoided due to performance considerations. MongoDB is not efficient for doing partial string matching except for the special case where you only want a prefix match. Queries that have regular expression constraints are therefore very expensive, especially for classes with over 100,000 records. Consider restricting how many such operations can be run on a particular app at any given time.

You should avoid using regular expression constraints that don’t use indexes. For example, the following query looks for data with a given string in the “playerName” field. The string search is case insensitive and therefore cannot be indexed:

The following query, while case sensitive, looks for any occurrence of the string in the field and cannot be indexed:

These queries are both slow. In fact, the matches and contains query constraints are not covered in our querying guides on purpose and we do not recommend using them. Depending on your use case, you should switch to using the following constraint that uses an index, such as:

This looks for data that starts with the given string. This query will use the backend index, so it will be faster even for large datasets.

As a best practice, when you use regular expression constraints, you’ll want to ensure that other constraints in the query reduce the result set to the order of hundreds of objects to make the query efficient. If you must use the matches or contains constraints for legacy reasons, then use case sensitive, anchored queries where possible, for example:

Most of the use cases around using regular expressions involve implementing search. A more performant way of implementing search is detailed later.

Write Restrictive Queries

Writing restrictive queries allows you to return only the data that the client needs. This is critical in a mobile environment were data usage can be limited and network connectivity unreliable. You also want your mobile app to appear responsive and this is directly affected by the objects you send back to the client. The Querying section shows the types of constraints you can add to your existing queries to limit the data returned. When adding constraints, you want to pay attention and design efficient queries.

You can use skip and limit to page through results and load the data as is needed. The query limit is 100 by default:

If you’re issuing queries on GeoPoints, make sure you specify a reasonable radius:

You can further limit the fields returned by calling select:

Client-side Caching

For queries run from iOS and Android, you can turn on query caching. See the iOS and Android guides for more details. Caching queries will increase your mobile app’s performance especially in cases where you want to display cached data while fetching the latest data from Parse.

Use Cloud Code

Cloud Code allows you to run custom JavaScript logic on Parse Server instead of on the client.

You can use this to offload processing to the Parse servers thus increasing your app’s perceived performance. You can create hooks that run whenever an object is saved or deleted. This is useful if you want to validate or sanitize your data. You can also use Cloud Code to modify related objects or kick off other processes such as sending off a push notification.

We saw examples of limiting the data returned by writing restrictive queries. You can also use Cloud Functions to help limit the amount of data returned to your app. In the following example, we use a Cloud Function to get a movie’s average rating:

Parse.Cloud.define("averageStars", async (request) => {
  const query = new Parse.Query("Review");
  query.equalTo("movie", request.params.movie);
  const results = await query.find();
  let sum = 0;
  for (let i = 0; i < results.length; ++i) {
    sum += results[i].get("stars");
  }
  return sum / results.length;
});

You could have ran a query on the Review class on the client, returned only the stars field data and computed the result on the client. As the number of reviews for a movie increases you can see that the data being returned to the device using this methodology also increases. Implementing the functionality through a Cloud Function returns the one result if successful.

As you look at optimizing your queries, you’ll find that you may have to change the queries - sometimes even after you’ve shipped your app to the App Store or Google Play. The ability to change your queries without a client update is possible if you use Cloud Functions. Even if you have to redesign your schema, you could make all the changes in your Cloud Functions while keeping the client interface the same to avoid an app update. Take the average stars Cloud Function example from before, calling it from a client SDK would look like this:

If later on, you need to modify the underlying data model, your client call can remain the same, as long as you return back a number that represents the ratings result.

Avoid Count Operations

When counting objects frequently, instead consider storing a count variable in the database that is incremented each time an object is added. Then, the count can quickly be retrieved by simply retrieving the variable stored.

Suppose you are displaying movie information in your app and your data model consists of a Movie class and a Review class that contains a pointer to the corresponding movie. You might want to display the review count for each movie on the top-level navigation screen using a query like this:

If you run the count query for each of the UI elements, they will not run efficiently on large data sets. One approach to avoid using the count() operator could be to add a field to the Movie class that represents the review count for that movie. When saving an entry to the Review class you could increment the corresponding movie’s review count field. This can be done in an afterSave handler:

Parse.Cloud.afterSave("Review", function(request) {
  // Get the movie id for the Review
  var movieId = request.object.get("movie").id;
  // Query the Movie represented by this review
  var Movie = Parse.Object.extend("Movie");
  var query = new Parse.Query(Movie);
  query.get(movieId).then(function(movie) {
    // Increment the reviews field on the Movie object
    movie.increment("reviews");
    movie.save();
  }, function(error) {
    throw "Got an error " + error.code + " : " + error.message;
  });
});

Your new optimized query would not need to look at the Review class to get the review count:

You could also use a separate Parse Object to keep track of counts for each review. Whenever a review gets added or deleted, you can increment or decrement the counts in an afterSave or afterDelete Cloud Code handler. The approach you choose depends on your use case.

Implement Efficient Searches

As mentioned previously, MongoDB is not efficient for doing partial string matching. However, this is an important use case when implementing search functionality that scales well in production.

Simplistic search algorithms simply scan through all the class data and executes the query on each entry. The key to making searches run efficiently is to minimize the number of data that has to be examined when executing each query by using an index as we’ve outlined earlier. You’ll need to build your data model in a way that it’s easy for us to build an index for the data you want to be searchable. For example, string matching queries that don’t match an exact prefix of the string won’t be able to use an index leading to timeout errors as the data set grows.

Let’s walk through an example of how you could build an efficient search. You can apply the concepts you learn in this example to your use case. Say your app has users making posts, and you want to be able to search those posts for hashtags or particular keywords. You’ll want to pre-process your posts and save the list of hashtags and words into array fields. You can do this processing either in your app before saving the posts, or you can use a Cloud Code beforeSave hook to do this on the fly:

var _ = require("underscore");
Parse.Cloud.beforeSave("Post", request => {
  var post = request.object;
  var toLowerCase = function(w) { return w.toLowerCase(); };
  var words = post.get("text").split(/\b/);
  words = _.map(words, toLowerCase);
  var stopWords = ["the", "in", "and"]
  words = _.filter(words, function(w) {
    return w.match(/^\w+$/) && !   _.contains(stopWords, w);
  });
  var hashtags = post.get("text").match(/#.+?\b/g);
  hashtags = _.map(hashtags, toLowerCase);
  post.set("words", words);
  post.set("hashtags", hashtags);
});

This saves your words and hashtags in array fields, which MongoDB will store with a multi-key index. There are some important things to notice about this. First of all it’s converting all words to lower case so that we can look them up with lower case queries, and get case insensitive matching. Secondly, it’s filtering out common words like ‘the’, ‘in’, and ‘and’ which will occur in a lot of posts, to additionally reduce useless scanning of the index when executing the queries.

Once you’ve got the keywords set up, you can efficiently look them up using “All” constraint on your query:

Limits and Other Considerations

There are some limits in place to ensure the API can provide the data you need in a performant manner. We may adjust these in the future. Please take a moment to read through the following list:

Objects

  • We recommend against storing large pieces of binary data like images or documents in a Parse Object.
  • We recommend against creating more than 64 fields on a single Parse Object to ensure that we can build effective indexes for your queries.
  • We recommend against using field names that are longer than 1,024 characters, otherwise an index for the field will not be created.

Queries

  • Queries return 100 objects by default. Use the limit parameter to change this.
  • Skips and limits can only be used on the outer query.
  • Constraints that collide with each other will result in only one of the constraints being applied. An example of this would be two equalTo constraints over the same key with two different values, which contradicts itself (perhaps you’re looking for ‘contains’).
  • No geo-queries inside compound OR queries.
  • Using $exists: false is not advised.
  • The each query method in the JavaScript SDK cannot be used in conjunction with queries using geo-point constraints.
  • A containsAll query constraint can only take up to 9 items in the comparison array.

Push Notifications

  • Delivery of notifications is a “best effort”, not guaranteed. It is not intended to deliver data to your app, only to notify the user that there is new data available.

Cloud Code

  • The params payload that is passed to a Cloud Function is limited to 50 MB.

Error Codes

The following is a list of all the error codes that can be returned by the Parse API. You may also refer to RFC2616 for a list of http error codes. Make sure to check the error message for more details.

API Issues

Name

Code

Description

UserInvalidLoginParams

101

Invalid login parameters. Check error message for more details.

ObjectNotFound

101

The specified object or session doesn’t exist or could not be found. Can also indicate that you do not have the necessary permissions to read or write this object. Check error message for more details.

InvalidQuery

102

There is a problem with the parameters used to construct this query. This could be an invalid field name or an invalid field type for a specific constraint. Check error message for more details.

InvalidClassName

103

Missing or invalid classname. Classnames are case-sensitive. They must start with a letter, and a-zA-Z0-9_ are the only valid characters.

MissingObjectId

104

An unspecified object id.

InvalidFieldName

105

An invalid field name. Keys are case-sensitive. They must start with a letter, and a-zA-Z0-9_ are the only valid characters. Some field names may be reserved. Check error message for more details.

InvalidPointer

106

A malformed pointer was used. You would typically only see this if you have modified a client SDK.

InvalidJSON

107

Badly formed JSON was received upstream. This either indicates you have done something unusual with modifying how things encode to JSON, or the network is failing badly. Can also indicate an invalid utf-8 string or use of multiple form encoded values. Check error message for more details.

CommandUnavailable

108

The feature you tried to access is only available internally for testing purposes.

NotInitialized

109

You must call Parse.initialize before using the Parse library. Check the Quick Start guide for your platform.

ObjectTooLarge

116

The object is too large.

ExceededConfigParamsError

116

You have reached the limit of 100 config parameters.

InvalidLimitError

117

An invalid value was set for the limit. Check error message for more details.

InvalidSkipError

118

An invalid value was set for skip. Check error message for more details.

OperationForbidden

119

The operation isn’t allowed for clients due to class-level permissions. Check error message for more details.

CacheMiss

120

The result was not found in the cache.

InvalidNestedKey

121

An invalid key was used in a nested JSONObject. Check error message for more details.

InvalidACL

123

An invalid ACL was provided.

InvalidEmailAddress

125

The email address was invalid.

DuplicateValue

137

Unique field was given a value that is already taken.

InvalidRoleName

139

Role’s name is invalid.

ReservedValue

139

Field value is reserved.

ExceededCollectionQuota

140

You have reached the quota on the number of classes in your app. Please delete some classes if you need to add a new class.

ScriptFailed

141

Cloud Code script failed. Usually points to a JavaScript error. Check error message for more details.

FunctionNotFound

141

Cloud function not found. Check that the specified Cloud function is present in your Cloud Code script and has been deployed.

JobNotFound

141

Background job not found. Check that the specified job is present in your Cloud Code script and has been deployed.

ValidationFailed

142

Cloud Code validation failed.

WebhookError

143

Webhook error.

InvalidImageData

150

Invalid image data.

UnsavedFileError

151

An unsaved file.

InvalidPushTimeError

152

An invalid push time was specified.

HostingError

158

Hosting error.

InvalidEventName

160

The provided analytics event name is invalid.

ClassNotEmpty

255

Class is not empty and cannot be dropped.

AppNameInvalid

256

App name is invalid.

MissingAPIKeyError

902

The request is missing an API key.

InvalidAPIKeyError

903

The request is using an invalid API key.

Name

Code

Description

IncorrectType

111

A field was set to an inconsistent type. Check error message for more details.

InvalidChannelName

112

Invalid channel name. A channel name is either an empty string (the broadcast channel) or contains only a-zA-Z0-9_ characters and starts with a letter.

InvalidSubscriptionType

113

Bad subscription type. Check error message for more details.

InvalidDeviceToken

114

The provided device token is invalid.

PushMisconfigured

115

Push is misconfigured in your app. Check error message for more details.

PushWhereAndChannels

115

Can’t set channels for a query-targeted push. You can fix this by moving the channels into your push query constraints.

PushWhereAndType

115

Can’t set device type for a query-targeted push. You can fix this by incorporating the device type constraints into your push query.

PushMissingData

115

Push is missing a ‘data’ field.

PushMissingChannels

115

Non-query push is missing a ‘channels’ field. Fix by passing a ‘channels’ or ‘query’ field.

ClientPushDisabled

115

Client-initiated push is not enabled. Check your Parse app’s push notification settings.

RestPushDisabled

115

REST-initiated push is not enabled. Check your Parse app’s push notification settings.

ClientPushWithURI

115

Client-initiated push cannot use the “uri” option.

PushQueryOrPayloadTooLarge

115

Your push query or data payload is too large. Check error message for more details.

InvalidExpirationError

138

Invalid expiration value.

MissingPushIdError

156

A push id is missing. Deprecated.

MissingDeviceTypeError

157

The device type field is missing. Deprecated.

Name

Code

Description

InvalidFileName

122

An invalid filename was used for Parse File. A valid file name contains only a-zA-Z0-9_. characters and is between 1 and 128 characters.

MissingContentType

126

Missing content type.

MissingContentLength

127

Missing content length.

InvalidContentLength

128

Invalid content length.

FileTooLarge

129

File size exceeds maximum allowed.

FileSaveError

130

Error saving a file.

FileDeleteError

153

File could not be deleted.

FileDeleteUnnamedError

161

Unnamed file could not be deleted.

Name

Code

Description

InvalidInstallationIdError

132

Invalid installation id.

InvalidDeviceTypeError

133

Invalid device type.

InvalidChannelsArrayError

134

Invalid channels array value.

MissingRequiredFieldError

135

Required field is missing.

ChangedImmutableFieldError

136

An immutable field was changed.

Name

Code

Description

ReceiptMissing

143

Product purchase receipt is missing.

InvalidPurchaseReceipt

144

Product purchase receipt is invalid.

PaymentDisabled

145

Payment is disabled on this device.

InvalidProductIdentifier

146

The product identifier is invalid.

ProductNotFoundInAppStore

147

The product is not found in the App Store.

InvalidServerResponse

148

The Apple server response is not valid.

ProductDownloadFilesystemError

149

The product fails to download due to file system error.

Name

Code

Description

UsernameMissing

200

The username is missing or empty.

PasswordMissing

201

The password is missing or empty.

UsernameTaken

202

The username has already been taken.

UserEmailTaken

203

Email has already been used.

UserEmailMissing

204

The email is missing, and must be specified.

UserWithEmailNotFound

205

A user with the specified email was not found.

SessionMissing

206

A user object without a valid session could not be altered.

MustCreateUserThroughSignup

207

A user can only be created through signup.

AccountAlreadyLinked

208

An account being linked is already linked to another user.

InvalidSessionToken

209

The device’s session token is no longer valid. The application should ask the user to log in again.

Linked services errors

Name

Code

Description

LinkedIdMissing

250

A user cannot be linked to an account because that account’s id could not be found.

InvalidLinkedSession

251

A user with a linked (e.g. Facebook or Twitter) account has an invalid session. Check error message for more details.

InvalidGeneralAuthData

251

Invalid auth data value used.

BadAnonymousID

251

Anonymous id is not a valid lowercase UUID.

FacebookBadToken

251

The supplied Facebook session token is expired or invalid.

FacebookBadID

251

A user with a linked Facebook account has an invalid session.

FacebookWrongAppID

251

Unacceptable Facebook application id.

TwitterVerificationFailed

251

Twitter credential verification failed.

TwitterWrongID

251

Submitted Twitter id does not match the id associated with the submitted access token.

TwitterWrongScreenName

251

Submitted Twitter handle does not match the handle associated with the submitted access token.

TwitterConnectFailure

251

Twitter credentials could not be verified due to problems accessing the Twitter API.

UnsupportedService

252

A service being linked (e.g. Facebook or Twitter) is unsupported. Check error message for more details.

UsernameSigninDisabled

252

Authentication by username and password is not supported for this application. Check your Parse app’s authentication settings.

AnonymousSigninDisabled

252

Anonymous users are not supported for this application. Check your Parse app’s authentication settings.

FacebookSigninDisabled

252

Authentication by Facebook is not supported for this application. Check your Parse app’s authentication settings.

TwitterSigninDisabled

252

Authentication by Twitter is not supported for this application. Check your Parse app’s authentication settings.

InvalidAuthDataError

253

An invalid authData value was passed. Check error message for more details.

LinkingNotSupportedError

999

Linking to an external account not supported yet with signup_or_login. Use update instead.

Client-only errors

Name

Code

Description

ConnectionFailed

100

The connection to the Parse servers failed.

AggregateError

600

There were multiple errors. Aggregate errors have an “errors” property, which is an array of error objects with more detail about each error that occurred.

FileReadError

601

Unable to read input for a Parse File on the client.

XDomainRequest

602

A real error code is unavailable because we had to use an XDomainRequest object to allow CORS requests in Internet Explorer, which strips the body from HTTP responses that have a non-2XX status code.

Operational issues

Name

Code

Description

RequestTimeout

124

The request was slow and timed out. Typically this indicates that the request is too expensive to run. You may see this when a Cloud function did not finish before timing out, or when a Parse.Cloud.httpRequest connection times out.

InefficientQueryError

154

An inefficient query was rejected by the server. Refer to the Performance Guide and slow query log.

Other issues

Name

Code

Description

OtherCause

-1

An unknown error or an error unrelated to Parse occurred.

InternalServerError

1

Internal server error. No information available.

ServiceUnavailable

2

The service is currently unavailable.

ClientDisconnected

4

Connection failure.

Configuring File Upload

Available on Parse Server >=5.0.0

Parse Server restricts file upload to authenticated users only to improve Parse Server’s default security. This behaviour can be modified by specifying fileUpload options to your Parse Server configuration.

Available options are:

  • enableForAnonymousUser: Enable file upload for anonymous users
  • enableForAuthenticatedUser: Enable file upload for authenticated users
  • enableForPublic: Enable file upload for the public, i.e. everyone

To allow public file uploads to Parse Server:

const api = new ParseServer({
  databaseURI: databaseUri || 'mongodb://localhost:27017/dev',
  cloud: process.env.PARSE_SERVER_CLOUD || __dirname + '/cloud/main.js',
  appId: process.env.PARSE_SERVER_APPLICATION_ID || 'myAppId',
  masterKey: process.env.PARSE_SERVER_MASTER_KEY || '',
  fileUpload: {
    enableForPublic: true,
    enableForAnonymousUser: true,
    enableForAuthenticatedUser: true,
  }
});
await api.start();

Configuring File Adapters

Parse Server allows developers to choose from several options when hosting files:

  • GridStoreAdapter, which is backed by MongoDB;
  • S3Adapter, which is backed by Amazon S3; or
  • GCSAdapter, which is backed by Google Cloud Storage; or
  • FSAdapter, local file storage

GridStoreAdapter is used by default and requires no setup, but if you’re interested in using S3 or Google Cloud Storage, additional configuration information is available below.

When using files on Parse, you will need to use the publicServerURL option in your Parse Server config. This is the URL that files will be accessed from, so it should be a URL that resolves to your Parse Server. Make sure to include your mount point in this URL.

When using Postgres, you will need to configure S3Adapter, GCSAdapter, or FSAdapter for file support. The GridStoreAdapter does not work with Postgres.

Configuring GridStoreAdapter

If you are using Mongo and don’t need file encryption, there are no additional steps needed to use the GridStoreAdapter. If you’d like to enable file encryption follow these instructions to configure Parse Server to use GridStoreAdapter with file encryption.

Set up file encryption

File encryption is available in parse-server 4.4.0+. The GridStoreAdapter can encrypt files at rest in Mongo using AES256-GCM, allowing the adapter to detect if files are tampered with.

To use, simply do any of the following:

  • Use the environment variable PARSE_SERVER_ENCRYPTION_KEY
  • Pass the encryption key via parameter --encryptionKey in the command line when starting Parse Server
  • Initialize Parse Server with encryptionKey="PATH_TO_ENCRYPTION_KEY_FILE.

An example starting your Parse Server in index.js is below:

const api = new ParseServer({
  databaseURI: databaseUri || 'mongodb://localhost:27017/dev',
  cloud: process.env.PARSE_SERVER_CLOUD || __dirname + '/cloud/main.js',
  appId: process.env.PARSE_SERVER_APPLICATION_ID || 'myAppId',
  masterKey: process.env.PARSE_SERVER_MASTER_KEY || '',
  encryptionKey: process.env.PARSE_SERVER_ENCRYPTION_KEY, //Add your file key here. Keep it secret
  ...
});
await api.start();

Be sure not to lose your key or change it after encrypting files.

Enabling encryption on a server that already has unencrypted files

When this is the case, it is recommended to start up a development parse-server (or a separate process from your main process) that has the same configuration as your production server. On the development server, initialize the file adapter as above with the new key and do the following after initialization in your index.js:

//You probably want to back up your unencrypted files before doing this.
//This can take awhile depending on how many files and how large they are. It will attempt to rotate the key of all files in your filesSubDirectory
const {rotated, notRotated} =  await api.filesAdapter.rotateEncryptionKey();
console.log('Files rotated to newKey: ' + rotated);
console.log('Files that couldn't be rotated to newKey: ' + notRotated);

Rotating your encryption key

Periodically you may want to rotate your encryptionKey for security reasons. When this is the case, it is recommended to start up a development parse-server (or a separate process from your main process) that has the same configuration as your production server. On the development server, initialize the file adapter with the new key and do the following in your index.js (you will need your oldKey):

//This can take awhile depending on how many files and how large they are. It will attempt to rotate the key of all files in your filesSubDirectory
const {rotated, notRotated} =  await api.filesAdapter.rotateEncryptionKey({oldKey: oldKey});
console.log('Files rotated to newKey: ' + rotated);
console.log('Files that couldn't be rotated to newKey: ' + notRotated);

Removing file encryption

When this is the case, it is recommended to start up a development parse-server (or a separate process from your main process) that has the same configuration as your production server. Different from the previous examples, don’t initialize your fileAdapter with a encryptionKey. Pass in your oldKey to rotateEncryptionKey().

const api = new ParseServer({
  databaseURI: databaseUri || 'mongodb://localhost:27017/dev',
  cloud: process.env.PARSE_SERVER_CLOUD || __dirname + '/cloud/main.js',
  appId: process.env.PARSE_SERVER_APPLICATION_ID || 'myAppId',
  masterKey: process.env.PARSE_SERVER_MASTER_KEY || '',
  //No encryptionKey here
  ...
});
await api.start();

//This can take awhile depending on how many files and how larger they are. It will attempt to rotate the key of all files in your filesSubDirectory
//It is not recommended to do this on the production server, deploy a development server to complete the process.
const {rotated, notRotated} =  await api.filesAdapter.rotateEncryptionKey({oldKey: oldKey});
console.log('Files rotated to unencrypted with noKey: ' + rotated);
console.log('Files that couldn't be rotated to unencrypted with noKey: ' + notRotated);

Rotating the key for a subset of files

This is useful if for some reason there were errors and some of the files weren’t rotated and returned in notRotated. The process is the same as the previous examples, but pass in your oldKey along with the array of fileNames to rotateEncryptionKey().

//This can take awhile depending on how many files and how large they are. It will attempt to rotate the key of all files in your filesSubDirectory
const {rotated, notRotated} =  await api.filesAdapter.rotateEncryptionKey({oldKey: oldKey, fileNames: ["fileName1.png","fileName2.png"]});
console.log('Files rotated to newKey: ' + rotated);
console.log('Files that couldn't be rotated to newKey: ' + notRotated);

Configuring S3Adapter

If you’d like to use Amazon S3, follow these instructions to configure Parse Server to use S3Adapter.

Set up your bucket and permissions

First you will create a bucket in S3 to hold these files.

  1. Log into your AWS account or create a new one.

  2. Head to the S3 service and choose Create Bucket

  3. Fill out a unique Bucket Name and click Create. The bucket name should not contain any period ‘.’ for directAccess to work. All other defaults are OK.

  4. Now head to the Identity and Access Management (IAM) service.

  5. Click the Users tab, then Create New User.

  6. Fill out at least one user name and make sure Generate an access key for each user is selected before clicking Create.

  7. Make sure to Download Credentials on the next screen.

  8. Now select the Policies tab, then Create Policy.

  9. Select Create Your Own Policy, fill out a Policy Name.

  10. Copy the following config in Policy Document, changing BUCKET_NAME for the name of the bucket you created earlier. (note: this is a little more permissive than Parse Server needs, but it works for now)

    { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:" ], "Resource": [ "arn:aws:s3:::BUCKET_NAME", "arn:aws:s3:::BUCKET_NAME/" ] } ] }

  1. Make sure to Validate Policy first, then click Create Policy.
  2. Go back to the Users tab and select the user you created earlier.
  3. In Permissions, select Attach Policy and find the policy we just created to attach it.

Configuration options

Writing to your Amazon S3 bucket from Parse Server is as simple as configuring and using the S3 files adapter.

Using environment variables

If you’re running a standalone Parse Server, you can use the following environment variables to configure the S3 adapter:

Variable Name

Description

Notes

PARSE_SERVER_FILES_ADAPTER

Set this variable to ‘./Files/S3Adapter.js’.

Required

S3_ACCESS_KEY

The AWS access key for a user that has the required permissions.

Required

S3_SECRET_KEY

The AWS secret key for the user.

Required

S3_BUCKET

The name of your S3 bucket. Needs to be globally unique in all of S3.

Required

S3_REGION

The AWS region to connect to.

Optional. Default: ‘us-east-1’

S3_BUCKET_PREFIX

Create all the files with the specified prefix added to the filename. Can be used to put all the files for an app in a folder with ‘folder/’.

Optional.

S3_DIRECT_ACCESS

Whether reads are going directly to S3 or proxied through your Parse Server. If set to true, files will be made publicly accessible, and reads will not be proxied.

Optional. Default: false

Passing as options

If you’re using Node.js/Express:

...
var S3Adapter = require('parse-server').S3Adapter;

var api = new ParseServer({
  databaseURI: databaseUri || 'mongodb://localhost:27017/dev',
  appId: process.env.APP_ID || 'APPLICATION_ID',
  masterKey: process.env.MASTER_KEY || 'MASTER_KEY',
  ...
  filesAdapter: new S3Adapter(
    "S3_ACCESS_KEY",
    "S3_SECRET_KEY",
    "S3_BUCKET",
    {directAccess: true}
  ),
  ...
});

Don’t forget to change S3_ACCESS_KEY, S3_SECRET_KEY and S3_BUCKET to their correct value.

Adapter Options

new S3Adapter(accessKey, secretKey, bucket, options)

Parameter

Description

Notes

accessKey

The AWS access key for a user that has the required permissions

Required.

secretKey

The AWS secret key for the user

Required.

bucket

The name of your S3 bucket.

Required.

options

JavaScript object (map) that can contain:

region

Key in options. Set the AWS region to connect to.

Optional. Default: us-east-1

bucketPrefix

Key in options. Set to create all the files with the specified prefix added to the filename. Can be used to put all the files for an app in a folder with ‘folder/’.

Optional. Default: null

directAccess

Key in options. Controls whether reads are going directly to S3 or proxied through your Parse Server. If set to true, files will be made publicly accessible, and reads will not be proxied.

Optional. Default: false

baseUrl

Key in options. The base URL the file adapter uses to determine the file location for direct access.

Optional. Default: null. To be used when directAccess=true. When set the file adapter returns a file URL in format baseUrl/bucketPrefix + filename. Example for baseUrl=’http://domain.com/folder’ and bucketPrefix=’prefix_’ the returned file location is http://domain.com/folder/prefix_file.txt.

baseUrlDirect

Key in options. Is true if the file adapter should ignore the bucket prefix when determining the file location for direct access.

Optional. Default: false. To be used when directAccess=true and baseUrl is set. When set to true, the file adapter returns a file URL in format baseUrl/filename. Example for baseUrl=’http://domain.com/folder’ and baseUrlDirect=true the returned file location is http://domain.com/folder/file.txt.

globalCacheControl

Key in options. The Cache-Control http header to set in the file request.

Optional. Default: null. Example: public, max-age=86400 for 24 hrs caching. More info here.

S3-compatible Services****Digital Ocean Spaces

Digital Ocean Spaces is an S3-compatible storage service in the cloud. See their documentation for more details.

const s3Options = {
  bucket: "SPACES_BUCKET_NAME",
  baseUrl: "SPACES_BASE_URL",
  region: "SPACES_REGION",
  bucketPrefix: "SPACES_BUCKET_PREFIX",
  s3overrides: {
    accessKeyId: "SPACES_ACCESS_KEY",
    secretAccessKey: "SPACES_SECRET_KEY",
    endpoint: 'SPACES_ENDPOINT'
  }
};

Linode Object Storage

Linode Object Storage is an S3-compatible storage service in the cloud. See their documentation for more details.

const s3Options = {
  bucket: "S3_BUCKET_NAME",
  baseUrl: "S3_BASE_URL", // https://myBucket.myRegion.linodeobjects.com
  region: "S3_REGION", // possible values: eu-central-1 or us-east-1
  s3overrides: {
    accessKeyId: "S3_ACCESS_KEY", // bucket access key
    secretAccessKey: "S3_SECRET_KEY", // bucket secret key
    endpoint: "S3_ENDPOINT", // regionName.linodeobjects.com
  },
};

Backblaze B2 Cloud Storage

Backblaze B2 Cloud Storage is an S3-compatible storage service in the cloud. See their documentation for more details.

const s3Options = {
  bucket: "S3_BUCKET",
  baseUrl: "S3_BASE_URL", // taken from BackBlaze, normally https://BUCKET.s3.REGION.backblazeb2.com
  signatureVersion: 'v4',
  region: 'us-west-000',
  s3overrides: {
    endpoint: "S3_ENDPOINT", // check backblaze bucket endpoint
    accessKeyId: "S3_ACCESS_KEY",
    secretAccessKey: "S3_SECRET_KEY"
  },
};

Configuring GCSAdapter

Unlike the S3 adapter, you must create a new Cloud Storage bucket, as this is not created automatically. See the Google Cloud guide on Authentication for more details.

To generate a private key in the Cloud Platform Console follow these instructions.

Installation

Starting 2.2.6, GCS Adapter is not provided by default by parse-server. To install run:

npm install --save parse-server-gcs-adapter

Configuration options

Writing to your Google Cloud Storage bucket from Parse Server is as simple as configuring and using the GCS files adapter.

Using environment variables

You can use Google Cloud Storage to host your static files by setting the following environment variables:

Variable Name

Description

Notes

PARSE_SERVER_FILES_ADAPTER

Set this variable to ‘parse-server-gcs-adapter’.

Required.

GCP_PROJECT_ID

The project ID from the Google Developer’s Console.

Required.

GCP_KEYFILE_PATH

Full path to the a .json, .pem, or .p12 key downloaded from the Google Developers Console.

Required.

GCS_BUCKET

The name of your GCS bucket.

Required.

GCS_BUCKET_PREFIX

Create all the files with the specified prefix added to the filename. Can be used to put all the files for an app in a folder with ‘folder/’.

Optional.

GCS_DIRECT_ACCESS

Whether reads are going directly to GCS or proxied through your Parse Server.

Optional. Default: false

Passing as options

If you’re using Node.js/Express:

...
var GCSAdapter = require('parse-server-gcs-adapter');

var api = new ParseServer({
  databaseURI: databaseUri || 'mongodb://localhost:27017/dev',
  appId: process.env.APP_ID || 'APPLICATION_ID',
  masterKey: process.env.MASTER_KEY || 'MASTER_KEY',
  ...
  filesAdapter: new GCSAdapter(
    "GCP_PROJECT_ID",
    "GCP_KEYFILE_PATH",
    "GCS_BUCKET",
    {directAccess: true}
  ),
  ...
});

GCSAdapter constructor options

new GCSAdapter(projectId, keyfilePath, bucket, options)

Parameter

Description

Notes

projectId

The project ID from the Google Developer’s Console.

Required.

keyfilePath

Full path to the a .json, .pem, or .p12 key downloaded from the Google Developers Console.

Required.

bucket

The name of your GCS bucket.

Required.

options

JavaScript object (map) that can contain:

bucketPrefix

Key in options. Set to create all the files with the specified prefix added to the filename. Can be used to put all the files for an app in a folder with ‘folder/’.

Optional. Default: ‘’

directAccess

Key in options. Controls whether reads are going directly to GCS or proxied through your Parse Server.

Optional. Default: false

Configuring FSAdapter

To use the FSAdapter, simply initialize your Parse Server in index.js by doing the following:

var FSFilesAdapter = require('@parse/fs-files-adapter');

var fsAdapter = new FSFilesAdapter({
  "filesSubDirectory": "my/files/folder" // optional, defaults to ./files
});

var api = new ParseServer({
    appId: 'my_app',
    masterKey: 'master_key',
    filesAdapter: fsAdapter
})

Using FSAdapter with multiple instances of Parse Server

When using parse-server-fs-adapter across multiple Parse Server instances it’s important to establish “centralization” of your file storage (this is the same premise as the other file adapters, you are sending/recieving files through a dedicated link). You can accomplish this at the file storage level by Samba mounting (or any other type of mounting) your storage to each of your parse-server instances, e.g if you are using Parse Server via docker (volume mount your SMB drive to - /Volumes/SMB-Drive/MyParseApp1/files:/parse-server/files). All Parse Server instances need to be able to read/write to the same storage in order for parse-server-fs-adapter to work properly with parse-server. If the file storage isn’t centralized, parse-server will have trouble locating files and you will get random behavior on client-side.

Set up file encryption

File encryption is available in parse-server-fs-adapter 1.1.0+. The FSAdapter can encrypt files at rest for local storage using AES256-GCM, allowing the adapter to detect if files are tampered with.

To use, simply do the same as above, but add a encryptionKey:

var FSFilesAdapter = require('@parse/fs-files-adapter');

var fsAdapter = new FSFilesAdapter({
  "filesSubDirectory": "my/files/folder", // optional, defaults to ./files
  "encryptionKey": "someKey" //mandatory if you want to encrypt files
});

var api = new ParseServer({
    appId: 'my_app',
    masterKey: 'master_key',
    filesAdapter: fsAdapter
})

Be sure not to lose your key or change it after encrypting files.

Enabling encryption on a server that already has unencrypted files

When this is the case, it is recommended to start up a development parse-server (or a separate process from your main process) that has the same configuration as your production server. On the development server, initialize the file adapter as above with the new key and do the following after initialization in your index.js:

//You probably want to back up your unencrypted files before doing this.
//This can take awhile depending on how many files and how large they are. It will attempt to rotate the key of all files in your filesSubDirectory
const {rotated, notRotated} =  await api.filesAdapter.rotateEncryptionKey();
console.log('Files rotated to newKey: ' + rotated);
console.log('Files that couldn't be rotated to newKey: ' + notRotated);

Rotating your encryption key

Periodically you may want to rotate your encryptionKey for security reasons. When this is the case, it is recommended to start up a development parse-server (or a separate process from your main process) that has the same configuration as your production server. On the development server, initialize the file adapter with the new key and do the following in your index.js (you will need your oldKey):

//This can take awhile depending on how many files and how large they are. It will attempt to rotate the key of all files in your filesSubDirectory
const {rotated, notRotated} =  await api.filesAdapter.rotateEncryptionKey({oldKey: oldKey});
console.log('Files rotated to newKey: ' + rotated);
console.log('Files that couldn't be rotated to newKey: ' + notRotated);

Removing file encryption

When this is the case, it is recommended to start up a development parse-server (or a separate process from your main process) that has the same configuration as your production server. Different from the previous examples, don’t initialize your fileAdapter with a encryptionKey. Pass in your oldKey to rotateEncryptionKey().

const api = new ParseServer({
  databaseURI: databaseUri || 'mongodb://localhost:27017/dev',
  cloud: process.env.PARSE_SERVER_CLOUD || __dirname + '/cloud/main.js',
  appId: process.env.PARSE_SERVER_APPLICATION_ID || 'myAppId',
  masterKey: process.env.PARSE_SERVER_MASTER_KEY || '',
  filesAdapter: new FSFilesAdapter(), //No encryptionKey supplied
  ...
});
await api.start();

//This can take awhile depending on how many files and how larger they are. It will attempt to rotate the key of all files in your filesSubDirectory
//It is not recommended to do this on the production server, deploy a development server to complete the process.
const {rotated, notRotated} =  await api.filesAdapter.rotateEncryptionKey({oldKey: oldKey});
console.log('Files rotated to unencrypted with noKey: ' + rotated);
console.log('Files that couldn't be rotated to unencrypted with noKey: ' + notRotated);

Rotating the key for a subset of files

This is useful if for some reason there were errors and some of the files weren’t rotated and returned in notRotated. The process is the same as the previous examples, but pass in your oldKey along with the array of fileNames to rotateEncryptionKey().

//This can take awhile depending on how many files and how large they are. It will attempt to rotate the key of all files in your filesSubDirectory
const {rotated, notRotated} =  await api.filesAdapter.rotateEncryptionKey({oldKey: oldKey, fileNames: ["fileName1.png","fileName2.png"]});
console.log('Files rotated to newKey: ' + rotated);
console.log('Files that couldn't be rotated to newKey: ' + notRotated);

Configuring Cache Adapters

By default, parse-server provides an internal cache layer to speed up schema verifications, user, roles and sessions lookup.

In some cases, in distributed environment, you may want to use a distributed cache like Redis.

parse-server comes with an optional redis cache adapter.

Those cache adapters can be cleaned at anytime internally, you should not use them to cache data and you should let parse-server manage their data lifecycle.

RedisCacheAdapter

var RedisCacheAdapter = require('parse-server').RedisCacheAdapter;
var redisOptions = {url: 'YOUR REDIS URL HERE'}
var redisCache = new RedisCacheAdapter(redisOptions);

var api = new ParseServer({
  databaseURI: databaseUri || 'mongodb://localhost:27017/dev',
  appId: process.env.APP_ID || 'APPLICATION_ID',
  masterKey: process.env.MASTER_KEY || 'MASTER_KEY',
  ...
  cacheAdapter: redisCache,
  ...
});

The redisOptions are passed directly to the redis.createClient method. For more information refer to the redis.createClient documentation.

Note that at the moment, only passing a single argument is supported.

The cache adapter can flush the redis database at anytime. It is best to not use the same redis database for other services. A different redis database can be chosen by providing a different database number to redisOptions. By default redis has 16 databases (indexed from 0 to 15).

Live Queries

Parse.Query is one of the key concepts for Parse. It allows you to retrieve Parse.Objects by specifying some conditions, making it easy to build apps such as a dashboard, a todo list or even some strategy games. However, Parse.Query is based on a pull model, which is not suitable for apps that need real-time support.

Suppose you are building an app that allows multiple users to edit the same file at the same time. Parse.Query would not be an ideal tool since you can not know when to query from the server to get the updates.

To solve this problem, we introduce Parse LiveQuery. This tool allows you to subscribe to a Parse.Query you are interested in. Once subscribed, the server will notify clients whenever a Parse.Object that matches the Parse.Query is created or updated, in real-time.

Parse LiveQuery contains two parts, the LiveQuery server and the LiveQuery clients. In order to use live queries, you need to set up both of them.

Server Setup

The LiveQuery server should work with a Parse Server. The easiest way to setup the LiveQuery server is to make it run with the Parse Server in the same process. When you initialize the Parse Server, you need to define which Parse.Object classes you want to enable LiveQuery like this:

let api = new ParseServer({
  ...,
  liveQuery: {
    classNames: ['Test', 'TestAgain']
  }
});

After that, you need to initialize a LiveQuery server like this:

// Initialize a LiveQuery server instance, app is the express app of your Parse Server
let httpServer = require('http').createServer(app);
httpServer.listen(port);
var parseLiveQueryServer = ParseServer.createLiveQueryServer(httpServer);

The ws protocol URL of the LiveQuery server is the hostname and port which the httpServer is listening to. For example, if the httpServer is listening to localhost:8080, the ws protocol of the LiveQuery server is ws://localhost:8080/. We will allow you to customize the path of ws protocol URL of the LiveQuery server later, currently it is fixed and you can not set path.

Client Setup

We provide JavaScript, Android and iOS LiveQuery Clients for now. Lets use the JavaScript client as an example. In order to use LiveQuery, you need to initialize a Parse.Query object and subscribe to it.

let query = new Parse.Query('People');
query.equalTo('name', 'Mengyan');
let subscription = await query.subscribe();

After you get the subscription, you can use it to receive the updates of the related Parse.Object. For example, if someone creates a People object whose name field is Mengyan, then you can get the People object like this:

subscription.on('create', (people) => {
  console.log(people.get('name')); // This should output Mengyan
});

After that, if someone updates this People object like changing its score to 100, then you can get the People object like this:

subscription.on('update', (people) => {
  console.log(people.get('score')); // This should output 100
});

If you are done with the LiveQuery, you can simply unsubscribe the subscription to finish receiving events

subscription.unsubscribe();

Events

We support five types of event:

  • create
  • enter
  • update
  • leave
  • delete

Further Reading

You can check the LiveQuery protocol specification to learn more about each event type.

For more details about the JavaScript LiveQuery Client SDK, check out the open source code and the Live Query section in the JavaScript Guide.

For the iOS LiveQuery Client SDK, check out the open source code.

LiveQuery Protocol

The LiveQuery Protocol is the key to the Parse LiveQuery. The clients and server communicate through WebSocket using this protocol. Clients can follow the protocol to connect to the LiveQuery server, subscribe/unsubscribe a Parse.Query and get updates from the LiveQuery server.

The LiveQuery protocol is a simple protocol that encapsulates messages in JSON strings and runs over a WebSocket connection. You can find the specification in the For the specification, check out the Parse Server wiki page.

LiveQuery Server****Configuring the server

The full configuration of the LiveQuery server should look like this:

{
  appId: 'myAppId',
  masterKey: 'myMasterKey',
  keyPairs: {
    "restAPIKey": "",
    "javascriptKey": "",
    "clientKey": "",
    "windowsKey": "",
    "masterKey": ""
  },
  serverURL: 'serverURL',
  websocketTimeout: 10 * 1000,
  cacheTimeout: 60 * 600 * 1000,
  logLevel: 'VERBOSE'
}

Options

  • appId - Required. This string should match the appId in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same appId.
  • masterKey - Required. This string should match the masterKey in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same masterKey.
  • serverURL - Required. This string should match the serverURL in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same serverURL.
  • keyPairs - Optional. A JSON object that serves as a whitelist of keys. It is used for validating clients when they try to connect to the LiveQuery server. Check the following Security section and our protocol specification for details.
  • websocketTimeout - Optional. Number of milliseconds between ping/pong frames. The WebSocket server sends ping/pong frames to the clients to keep the WebSocket alive. This value defines the interval of the ping/pong frame from the server to clients. Defaults to 10 * 1000 ms (10 s).
  • cacheTimeout - Optional. Number in milliseconds. When clients provide the sessionToken to the LiveQuery server, the LiveQuery server will try to fetch its ParseUser’s objectId from parse server and store it in the cache. The value defines the duration of the cache. Check the following Security section and our protocol specification for details. Defaults to 30 * 24 * 60 * 60 * 1000 ms (~30 days).
  • logLevel - Optional. This string defines the log level of the LiveQuery server. We support VERBOSE, INFO, ERROR, NONE. Defaults to INFO.

Basic Architecture

The LiveQuery server is a separate server from Parse Server. As shown in the picture, it mainly contains four components at the runtime.

  • The Publisher. It is responsible for publishing the update of a Parse.Object. When a Parse.Object changes, it will publish a message to the subscribers. The message contains the original Parse.Object and the new Parse.Object. The Publisher is inside the Parse Server at the runtime.
  • The Subscriber. It is responsible for receiving the messages which are sent from the Publisher. After it gets the messages, it can pass them to the LiveQuery component for processing.
  • The WebSocketServer. It is responsible for maintaining the WebSocket connections with clients. It can pass the subscribe/unsubscribe messages from clients to the LiveQuery component. When the LiveQuery component finds a Parse.Object fulfills a Parse.Query, it will get the event message from LiveQuery component and send it to the clients.
  • The LiveQuery. It is the key component of the LiveQuery Server. It maintains the subscription status of clients. After it gets the Parse.Object updates from the Subscriber, it can do the query matching and generate the event messages for clients.

Scalability

Based on your usage, different components of the LiveQuery server may become the bottleneck. If you app has high throughput, the Publisher/Subscriber may have problems. If you subscribe to many complex Parse.Querys, the LiveQuery component may cause issues. If you need to maintain lots of clients, the WebSocketServer may be the bottleneck. Thus, we highly recommend you to do the load testing for your app if you want to use LiveQuery server in production.

In general, our suggestion to make the LiveQuery server scalable is to separate the Parse Server with the LiveQuery server and add more LiveQuery server instances based on your need. To help you do this, we use Redis to implement a Publisher and Subscriber. If you want to use that, the only thing you need to do is to provide the Redis server address when you initialize the Parse Server and LiveQuery server like this:

let api = new ParseServer({
  ...,
  liveQuery: {
    classNames: ['Test', 'TestAgain'],
    redisURL: 'redis://localhost:6379'
  }
});

...

let httpServer = require('http').createServer(app);
httpServer.listen(port);
var parseLiveQueryServer = ParseServer.createLiveQueryServer(httpServer,  {
  ...,
  redisURL: 'redis://localhost:6379'
});

This redis database should be different from the redis database used for RedisCacheAdapter.

The architecture of the whole LiveQuery system after you use Redis should be like this:

For example, if you use Heroku to deploy your Live Query server, after you setup the Redis with the LiveQuery server, you can simply add more dynos to make your app more scalable like this:

Security with LiveQuery

The LiveQuery server provides two ways to secure your app. The first one is key matching. If you provide key pairs when you initialize the LiveQuery server, when clients try to connect to LiveQuery server, they have to provide the necessary key pairs. Otherwise, the connection will be refused.

The second one is ACL. For what is ACL, you can check the definition here. When clients try to connect and subscribe to the LiveQuery server, they can provide their sessionToken. If you give your Parse.Object proper ACL, when the LiveQuery server get the updates of the Parse.Object, it will try to match Parse.Object’s ACL with the sessionToken of clients or their subscriptions. The event will be only sent to clients whose sessionToken matches the Parse.Object’s ACL.

LiveQuery Clients

The JavaScript LiveQuery client is provided as part of the Parse JavaScript SDK as of version 1.8.0. A separate LiveQuery client library is available for iOS / OS X and Android.

  • Parse JavaScript SDK
  • Parse LiveQuery iOS / OS X
  • Parse LiveQuery Android

LiveQuery With NGINX

Please refer to the NGINX documentation in order to allow a proper handling of the LiveQuery server that relies on web sockets

OAuth and 3rd Party Authentication

Parse Server supports 3rd party authentication with

  • Apple
  • Apple Game Center
  • Facebook
  • Github
  • Google
  • Google Play Game Services
  • Instagram
  • Janrain Capture
  • Janrain Engage
  • Keycloak
  • LDAP
  • Line
  • LinkedIn
  • Meetup
  • Microsoft Graph
  • OAuth
  • PhantAuth
  • QQ
  • Spotify
  • Twitter
  • vKontakte
  • WeChat
  • Weibo

Configuration options for these 3rd-party modules is done with the auth option passed to Parse Server:

{
  auth: {
   twitter: {
     consumer_key: "", // REQUIRED
     consumer_secret: "" // REQUIRED
   },
   facebook: {
     appIds: "FACEBOOK APP ID"
   }
  }
}

Supported 3rd party authentications

Below, you will find all expected payloads for logging in with a 3rd party auth.

Note, most of them don’t require a server configuration so you can use them directly, without particular server configuration.

Facebook authData

{
  "facebook": {
    "id": "user's Facebook id number as a string",
    "access_token": "an authorized Facebook access token for the user",
    "expiration_date": "token expiration date of the format: yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
  }
}

The options passed to Parse Server:

{
  auth: {
    facebook: {
      appIds: ['appId1', 'appId2'], // If set, the app ID is used to validate the authentication token provided by the client when authenticating.
    },
  }
}

Learn more about Facebook login.

Twitter authData

{
  "twitter": {
    "id": "user's Twitter id number as a string",
    "consumer_key": "your application's consumer key",
    "consumer_secret": "your application's consumer secret",
    "auth_token": "an authorized Twitter token for the user with your application",
    "auth_token_secret": "the secret associated with the auth_token"
  }
}

The options passed to Parse Server:

{
  auth: {
    twitter: {
     consumer_key: "", // REQUIRED
     consumer_secret: "" // REQUIRED
   },
  }
}

Learn more about Twitter login.

Anonymous user authData

{
  "anonymous": {
    "id": "random UUID with lowercase hexadecimal digits"
  }
}

Apple authData

As of Parse Server 3.5.0 you can use Sign In With Apple.

{
  "apple": {
    "id": "user",
    "token": "the identity token for the user"
  }
}

Using Apple Sign In on a iOS device will give you a ASAuthorizationAppleIDCredential.user string for the user identifier, which can be match the sub component of the JWT identity token. Using Apple Sign In through the Apple JS SDK or through the REST service will only give you the JWT identity token (id_token) which you’ll have to decompose to obtain the user identifier in its sub component. As an example you could use something like JSON.parse(atob(token.split(“.”)[1])).sub.

Configuring parse-server for Sign In with Apple

{
  auth: {
   apple: {
     clientId: 'com.example.app', // optional, for extra validation; replace with the bundle ID provided by Apple.
   },
  }
}

Learn more about Sign In With Apple.

Github authData

{
  "github": {
    "id": "user's Github id (string)",
    "access_token": "an authorized Github access token for the user"
  }
}

Google authData

Google oauth supports validation of id_token’s and access_token’s.

{
  "google": {
    "id": "user's Google id (string)",
    "id_token": "an authorized Google id_token for the user (use when not using access_token)",
    "access_token": "an authorized Google access_token for the user (use when not using id_token)"
  }
}

Instagram authData

{
  "instagram": {
    "id": "user's Instagram id (string)",
    "access_token": "an authorized Instagram access token for the user",
    "apiURL": "an api url to make requests. Default: https://api.instagram.com/v1/"
  }
}

Keycloak authData

{
  "keycloak": {
    "access_token": "access token from keycloak JS client authentication",
    "id": "the id retrieved from client authentication in Keycloak",
    "roles": ["the roles retrieved from client authentication in Keycloak"],
    "groups": ["the groups retrieved from client authentication in Keycloak"]
  }
}

The authentication module will test if the authData is the same as the userinfo oauth call, by comparing the attributes.

Copy the JSON config file generated on Keycloak (tutorial) and paste it inside of a folder (Ex.: auth/keycloak.json) in your server.

The options passed to Parse Server:

{
  auth: {
    keycloak: {
      config: require(`./auth/keycloak.json`) // Required
    }
  }
}

Configuring Parse Server for LDAP

The LDAP module can check if a user can authenticate (bind) with the given credentials. Optionally, it can also check if the user is in a certain group. This check is done using a user specified query, called an LDAP Filter. The query should return all groups which the user is a member of. The cn attribute of the query results is compared to groupCn.

To build a query which works with your LDAP server, you can use a LDAP client like Apache Directory Studio.

{
  "ldap": {
    "url": "ldap://host:port",
    "suffix": "the root of your LDAP tree",
    "dn": "Bind dn.  is replaced with the id suppied in authData",
    "groupCn": "Optional. A group which the user must be a member of.",
    "groupFilter": "Optional. An LDAP filter for finding groups which the user is part of.  is replaced with the id supplied in authData."
  }
}

If either groupCN or groupFilter is not specified, the group check is not performed.

Example Configuration (this works with the public LDAP test server hosted by Forumsys):

{
  "ldap": {
    "url": "ldap://ldap.forumsys.com:389",
    "suffix": "dc=example,dc=com",
    "dn": "uid=, dc=example, dc=com",
    "groupCn": "Chemists",
    "groupFilter": "(&(uniqueMember=uid=,dc=example,dc=com)(objectClass=groupOfUniqueNames))"
  }
}

authData:

{
  "authData": {
    "ldap": {
      "id": "user id",
      "password": "password"
    }
  }
}

LinkedIn authData

{
  "linkedin": {
    "id": "user's LinkedIn id (string)",
    "access_token": "an authorized LinkedIn access token for the user",
    "is_mobile_sdk": true|false // set to true if you acquired the token through LinkedIn mobile SDK
  }
}

Meetup authData

{
  "meetup": {
    "id": "user's Meetup id (string)",
    "access_token": "an authorized Meetup access token for the user"
  }
}

Microsoft Graph authData

{
  "microsoft": {
    "id": "user's microsoft id (string)", // required
    "access_token": "an authorized microsoft graph access token for the user", // required
    "mail": "user's microsoft email (string)"
  }
}

Learn more about Microsoft Graph Auth Overview.

To get access on behalf of a user.

PhantAuth authData

As of Parse Server 3.7.0 you can use PhantAuth.

{
  "phantauth": {
    "id": "user's PhantAuth sub (string)",
    "access_token": "an authorized PhantAuth access token for the user",
  }
}

Learn more about PhantAuth.

QQ authData

{
  "qq": {
    "id": "user's QQ id (string)",
    "access_token": "an authorized QQ access token for the user"
  }
}

Spotify authData

{
  "spotify": {
    "id": "user's spotify id (string)",
    "access_token": "an authorized spotify access token for the user"
  }
}

vKontakte authData

{
  "vkontakte": {
    "id": "user's vkontakte id (string)",
    "access_token": "an authorized vkontakte access token for the user"
  }
}

Configuring parse-server for vKontakte

{
  auth: {
   vkontakte: {
     appSecret: "", // REQUIRED, your vkontakte application secret
     appIds: "" // REQUIRED, your vkontakte application id
   },
  }
}

WeChat authData

{
  "wechat": {
    "id": "user's wechat id (string)",
    "access_token": "an authorized wechat access token for the user"
  }
}

Weibo authData

{
  "weibo": {
    "id": "user's weibo id (string)",
    "access_token": "an authorized weibo access token for the user"
  }
}

Custom authentication

It is possible to leverage the OAuth support with any 3rd party authentication that you bring in.

{

  auth: {
   my_custom_auth: {
     module: "PATH_TO_MODULE" // OR object,
     option1: "",
     option2: "",
   }
  }
}

On this module, you need to implement and export those two functions validateAuthData(authData, options) {} and validateAppId(appIds, authData, options) {}.

For more information about custom auth please see the examples:

  • Facebook OAuth
  • Twitter OAuth
  • Instagram OAuth
  • Microsoft Graph OAuth

Using MongoDB Read Preference

As of ParseServer 2.5, it is possible to set a read preference for Mongo DB queries. For a discussion of Read Preference, limitations and use cases, see the Mongo documentation for Read Preference…

How to set Read Preference in ParseServer

Currently, read preference can only be set in cloud code. For an example see: cloud code examples

Development Guide****Running Parse Server for development

Normally, when you run a standalone Parse Server, the latest release that has been pushed to npm will be used. This is great if you are interested in just running Parse Server, but if you are developing a new feature or fixing a bug you will want to use the latest code on your development environment.

First, you will need to clone this repo if you haven’t done so yet.

git clone https://github.com/parse-community/parse-server.git

You can then link the parse-server module to the cloned repo and run npm install:

npm link parse-server path/to/cloned/repo
npm install

You can now start Parse Server using npm start:

npm start -- --appId APPLICATION_ID --masterKey MASTER_KEY --serverURL http://localhost:1337/parse

Notable Files

The following is a breakdown of the various files you will find in the Parse Server source code. Click on a filename to learn more about the purpose behind each file.

  • index.js - exposes the ParseServer constructor and mutates Parse.Cloud
  • analytics.js - handle the /events routes
  • Auth.js - Auth object, created to hold config/master/user information for requests
  • batch.js - batch handling implemented for PromiseRouter
  • cache.js - simple caching for the app and user sessions
  • classes.js - handle the /classes routes
  • Config.js - Config object, storage for the application configuration and some router information
  • crypto.js - uses bcrypt for password hashing and comparison
  • DatabaseAdapter.js - Interface for allowing the underlying database to be changed
  • ExportAdapter.js - DatabaseAdapter for MongoDB (default)
  • facebook.js - helper functions for accessing the Graph API
  • files.js - handle the /files routes
  • FilesAdapter.js - Interface for allowing the underlying file storage to be changed
  • FileLoggerAdapter.js - LoggerAdapter for logging info and error messages into local files (default)
  • functions.js - handle the /functions routes
  • GridStoreAdapter.js - FilesAdapter for storing uploaded files in GridStore/MongoDB (default)
  • installations.js - handle the /installations routes
  • LoggerAdapter.js - Interface for allowing the underlying logging transport to be changed
  • middlewares.js - Express middleware used during request processing
  • PromiseRouter.js - PromiseRouter uses promises instead of req/res/next middleware conventions
  • push.js - handle the /push route
  • rest.js - main interface for REST operations
  • RestQuery.js - RestQuery encapsulates everything needed for a ‘find’ operation from REST API format
  • RestWrite.js - RestWrite encapsulates everything needed for ‘create’ and ‘update’ operations from REST API format
  • roles.js - handle the /roles routes
  • Schema.js - Schema handles schema validation, persistence, and modification.
  • sessions.js - handle the /sessions and /logout routes
  • testing-routes.js - used by internal Parse integration tests
  • transform.js - transforms keys/values between Mongo and Rest API formats.
  • triggers.js - cloud code methods for handling database trigger events
  • users.js - handle the /users and /login routes

Contributing

We really want Parse to be yours, to see it grow and thrive in the open source community. Please see the Contributing to Parse Server notes.

Best Practice

This page is a work in progress and incomplete. If you have any suggestions, please open a pull request.

Security****Firewall

Protect all Parse Server endpoints using a Firewall to mitigate the risk of malicious attempts to scape user data, flood the database and DDoS attacks.

  • Use rate-limiting rules for public endpoints, for example limit the number of requests per IP address or per user.
  • Use very restrictive rules for private endpoints; for example limit access to Parse Dashboard to your personal network.

Optimization

The following is a list of design considerations to optimize data traffic and performance.

Database

  • Use short field names; field names need to be stored in the database just like the field values; short field names not only require less database storage but also reduce the data traffic between database, server and client.

Queries

  • Use select and exclude to transfer only the fields that you need instead of the whole object.

Related news

GHSA-fcv6-fg5r-jm9q: Trigger `beforeFind` not invoked in internal query pipeline when fetching pointer

### Impact A Parse Pointer can be used to access internal Parse Server classes. It can also be used to circumvent the `beforeFind` query trigger which can be an additional vulnerability for deployments where the `beforeFind` trigger is used as a security layer to modify an incoming query. ### Patches The vulnerability was fixed by implementing a patch in the internal query pipeline to prevent a Parse Pointer to be used to access internal Parse Server classes or circumvent the `beforeFind` trigger. ### Workarounds There is no known workaround to prevent a Parse Pointer to be used to access internal Parse Server classes. A workaround if a `beforeFind` trigger is used as a security layer is to instead use the Parse Server provided [security layers](https://docs.parseplatform.org/parse-server/guide/#security) to manage access levels with Class-Level Permissions and Object-Level Access Control. ### References - GitHub security advisory: https://github.com/parse-community/parse-server...

CVE: Latest News

CVE-2023-50976: Transactions API Authorization by oleiman · Pull Request #14969 · redpanda-data/redpanda
CVE-2023-6905
CVE-2023-6903
CVE-2023-6904
CVE-2023-3907