Docs Menu

Docs HomeAtlas App Services

Device Sync Permissions Guide

On this page

  • Using the Template Apps
  • About These Examples
  • Read & Write Own Data
  • Write Own Data, Read All Data
  • Administrator Privileges
  • Enable & Configure Custom User Data
  • Set Up Admin Privileges
  • Restricted News Feed
  • Enable & Configure Custom User Data
  • Create Authentication Trigger Function
  • Set Up Restricted Permissions
  • Create subscribeToUser Function
  • Dynamic Collaboration
  • Set Up Permissions
  • Adding a Collaborator
  • Security
  • User Search
  • Tiered Privileges
  • Enable & Configure Custom User Data
  • Create Authentication Trigger Function
  • Create joinTeam Function
  • Define Permissions

This page shows how to set up your Device Sync app's permissions for the following common use cases:

You can get some of these permissions models up and running quickly with our Device Sync Permissions Guide template apps. Each template app comes with a backend and a Node.js demo client. The backend has all functions, permissions, and triggers set up as described here. The demo client connects to the backend and runs through a simple script to demonstrate how the backend works.

You need an authenticated App Services CLI to use these templates.

App Services CLI is available on npm. To install the CLI on your system, ensure that you have Node.js installed and then run the following command in your shell:

npm install -g atlas-app-services-cli

See the CLI reference page for login instructions.

Once logged in, you can use the apps create command with the --template flag to instantiate a template.

appservices apps create --template=TEMPLATE_NAME

TEMPLATE_NAME can be one of the following:

Deploy a Template App

The examples here use default roles. This means the same permissions rules apply to all collections in your app. As your app grows in complexity, you might need collection-specific roles that only apply to some collections and not others. In particular, if a rule expression in a default role uses a "queryable field" that doesn't exist on objects in a certain collection, you can override the rules for that collection by providing a collection-specific role. See Role-based Permissions for more information.

In this case, users may read or write their own data, but not other users' data. Consider a notes app where the user wants to persist and share notes across their own devices but keep them private to their user account.

This strategy permits a user to create and edit a document if and only if that document's owner_id field equals the user's ID.

To set up the "Read & Write Own Data" strategy, follow the general steps:

  1. Log in to the Realm UI, and then click Sync in the left hand panel.

  2. Under Sync Type, choose Flexible.

  3. Set the toggle to enable Development Mode.

  4. Select the cluster you want to sync.

  5. Define a Database Name: select +Add a new database and type a name for the database Realm will use to store your synced objects. You can name it anything you want. A common strategy would be to name the database after the app you're making.

  6. Select Queryable Fields: type in owner_id. This allows your permissions expressions (which you'll set next) to use the any fields called owner_id.

  7. Skip Advanced Configuration, and then click Save Changes to enable Sync.

Now you need to configure the permissions. Navigate to the Rules page. Click the Default Roles and Filters button to edit the default roles. Use the template dropdown to select the template called "Users can only read and write their own data".

This populates the rule expression box with the following:

{
"name": "owner-read-write",
"apply_when": {},
"document_filters": {
"read": { "owner_id": "%%user.id" },
"write": { "owner_id": "%%user.id" }
},
"read": true,
"write": true
}

Note that the document_filters.read and document_filters.write expressions use the owner_id field we marked as "queryable" above. It also uses the %%user expansion to read the requesting user's id. App Services replaces (or "expands") the expansion at evaluation time with the corresponding value -- in this case, the user object.

When evaluating permissions on a given document, App Services checks document_filters.read and document_filters.write before the corresponding top-level read or write expressions. For example, if document_filters.write evaluates to false, write access is denied; no subsequent rules are checked. However, if document_filters.write evaluates to true, App Services checks the top-level write expression. In this case, write is true, so write access is granted on the whole document. For more information, see Device Sync-Compatible Permissions.

In this case, users can read all data, but write only their own data. Consider a recipe app where users can read all recipes and add new recipes. The recipes they add can be viewed by everyone using the app. Users may update or delete only the recipes they contributed.

To set up the "Write Own Data, Read All Data" strategy, follow these general steps:

  1. Log in to the Realm UI, and then click Sync in the left hand panel.

  2. Under Sync Type, choose Flexible.

  3. Set the toggle to enable Development Mode.

  4. Select the cluster you want to sync.

  5. Define a Database Name: select +Add a new database and type a name for the database Realm will use to store your synced objects. You can name it anything you want. A common strategy would be to name the database after the app you're making.

  6. Select Queryable Fields: type in owner_id. This allows your permissions expressions (which you'll set next) to use the any fields called owner_id.

  7. Skip Advanced Configuration, and then click Save Changes to enable Sync.

Now you need to configure the permissions. Navigate to the Rules page. Click the Default Roles and Filters button to edit the default roles. Use the template dropdown to select the template called "Users can read all data but only write their own data".

This populates the rule expression box with the following:

{
"name": "owner-write",
"apply_when": {},
"document_filters": {
"read": true,
"write": { "owner_id": "%%user.id" }
},
"read": true,
"write": true
}

Note that the document_filters.read expression is set to true, indicating that no matter which user is authenticated, they can read all of the data. The document_filters.write expression uses the owner_id field we marked as "queryable" above and uses the %%user expansion to match against the requesting user's id. App Services replaces ("expands") the expansion at evaluation time with the corresponding value -- in this case, the user object.

If and only if document_filters.write evaluates to true will App Services check the top-level write expression. When document_filters.write evaluates to false, write access is denied regardless of what the top-level write expression says. For more information, see Device Sync-Compatible Permissions.

In this permission strategy, users with a specific "administrator" role can read and write any document. Users who do not have the specified role can only read and write their own data. To make this strategy work, you first need to define which users have administrator permissions.

Atlas Apps allow you to associate custom user data in your cluster with application users. Using this feature, you can create a document with a field that indicates whether the user has administrative privileges. While there are many ways to set this up, one approach is to add a boolean property called isGlobalAdmin, which is set to true for those users with the elevated permissions. Another is to create a string field called role, in which one of the expected values might be "admin".

In the following example, the custom user object we'll create has an _id field, which corresponds to the user's ID, and 3 additional fields: firstName, lastName, and isGlobalAdmin:

{
"_id" : "1234",
"firstName": "Lily",
"lastName": "Realmster",
"isGlobalAdmin": true
}

Note

When using custom user data for permissions, never allow the client to write the custom user data object. Doing so would allow any user to grant themselves any permission. Instead, use system functions on the server side to update the custom user data object.

Atlas App Services stores MongoDB documents that correspond to custom user data in a linked MongoDB Atlas cluster. When you configure custom user data for your application, you specify the cluster, database, collection, and finally a User ID field, which maps a custom user data document to an authenticated user's ID.

To enable Custom User Data in the App Services UI, follow these steps:

  1. Click App Users in the left hand panel.

  2. Select the User Settings tab and find the Custom User Data section.

  3. Set the Enable Custom User Data toggle to On.

  4. Specify the following values:

    • Cluster Name: The name of a linked MongoDB cluster that will contain the custom user data database.

    • Database Name: The name of the MongoDB database that will contain the custom user data collection.

    • Collection Name: The name of the MongoDB collection that will contain custom user data.

  5. Specify the User ID field. Every document in the custom user data collection must have a field that maps to a specific user. The field must be present in every document that maps to a user, and must contain the user's ID as a string. We recommend that you use the standard _id field to store the user ID. MongoDB automatically places a constraint on the _id field, ensuring uniqueness.

    Note

    If two documents in this collection contain the same user ID value, App Services uses the first document that matches, which leads to unexpected results.

  6. Save and deploy the changes.

    Note

    Restart the Sync Session after Updating Custom User Data

    The sync server caches custom user data for the duration of the session. As a result, you must restart the sync session after updating custom user data to avoid a compensating write error. To restart the sync session, pause and resume all open Sync Sessions in the client application. For more information on pausing and resuming sync from the client SDKs, see your preferred SDK:

Tip

After you have custom user data enabled, you can implement the Admin Privileges strategy. To do so, follow these general steps:

  1. Log in to the Realm UI, and then click Sync in the left hand panel.

  2. Under Sync Type, choose Flexible.

  3. Set the toggle to enable Development Mode.

  4. Select the cluster you want to sync.

  5. Define a Database Name: select +Add a new database and type a name for the database Realm will use to store your synced objects. You can name it anything you want. A common strategy would be to name the database after the app you're making.

  6. Select Queryable Fields: type in owner_id. This allows your permissions expressions (which you'll set next) to use the any fields called owner_id.

  7. Skip Advanced Configuration, and then click Save Changes to enable Sync.

Now you need to configure the permissions. Navigate to the Rules page. Click the Default Roles and Filters button to edit the default roles. Use the template dropdown to select the template called "Users can read and write their own data, admins can read and write all data".

This populates the rule expression box with the following:

[
{
"name": "admin",
"apply_when": {
"%%user.custom_data.isGlobalAdmin": true
},
"document_filters": {
"read": true,
"write": true
},
"read": true,
"write": true
},
{
"name": "user",
"apply_when": {},
"document_filters": {
"read": { "owner_id": "%%user.id" },
"write": { "owner_id": "%%user.id" }
},
"read": true,
"write": true
}
]

Note

Change the default settings

This configuration has two default roles. The first defines the permissions for an administrator. Note that the auto-generated expression assumes there is a boolean field in the custom user data document named isGlobalAdmin. Depending on how you defined your custom user data document, you might need to change this.

The second default role specifies the rules for all other users. The default is to restrict user access to read and write only their own data. You can change either or both of these fields to true, enabling users to read and/or write all data. See the previous sections to learn more about these strategies.

Note

Template Available!

To use the backend template and get the demo client, run the following command:

appservices apps create --name=restricted-feed --template=flex-sync-guides.restricted-feed

Custom User Data Configuration Bug Workaround: Sometimes, the template generator does not copy the custom user data configuration to the new app correctly. You can fix this as follows: the appservices apps create command should have output some JSON about the app you just created. From this JSON, copy the "url" value (something like https://services.cloud.mongodb.com/groups/...) and visit that URL in your browser. Log in if prompted. From the app dashboard, in the left-hand panel, click App Users. Click Custom User Data. Ensure Enable Custom User Data is ON. If it was not on, turn it on and enter "mongodb-atlas", "Item", and "User" for Cluster Name, Database Name, and Collection Name, respectively. For User ID Field, enter _id. Hit Save (or Save Draft, then deploy).

Next, in your terminal, go into the client directory, install the dependencies, and run the demo:

cd restricted-feed/frontend/flex-sync-guides.restricted-feed/
npm install
npm run demo

Read the output on your console to see what the demo is doing.

In this permission strategy, users can create their own content and subscribe to other creators' content. As with the Admin Privileges scenario, we will make use of a Custom User Data collection to define which authors' content a user is subscribed to read.

Flexible Device Sync supports querying arrays, so we will create an array within a user data object. This array contains IDs of the authors that this user is authorized to "follow". We then set up a subscription that says, in essence, "Give me all documents where I am the author, or the author's ID is in the array of authors in my custom user data."

Important

When a user subscribes or unsubscribes from an author, we update the array in the custom user data, but the changes don't take effect until the current session is closed and a new session is started.

Note

Size Limitations

In this example, we are creating an array in the Custom User Data. The size of this array is not limited by App Services, but because the data is included in each request, we recommend keeping the size under 16KB, which is enough space for 1000 128-bit GUID-style user IDs.

Note

When using custom user data for permissions, never allow the client to write the custom user data object. Doing so would allow any user to grant themselves any permission. Instead, use system functions on the server side to update the custom user data object.

To enable Custom User Data in the App Services UI, follow these steps:

  1. Click App Users in the left hand panel.

  2. Select the User Settings tab and find the Custom User Data section.

  3. Set the Enable Custom User Data toggle to On.

  4. Specify the following values:

    • Cluster Name: The name of a linked MongoDB cluster that will contain the custom user data database.

    • Database Name: The name of the MongoDB database that will contain the custom user data collection.

    • Collection Name: The name of the MongoDB collection that will contain custom user data.

  5. Specify the User ID field. Every document in the custom user data collection must have a field that maps to a specific user. The field must be present in every document that maps to a user, and must contain the user's ID as a string. We recommend that you use the standard _id field to store the user ID. MongoDB automatically places a constraint on the _id field, ensuring uniqueness.

    Note

    If two documents in this collection contain the same user ID value, App Services uses the first document that matches, which leads to unexpected results.

  6. Save and deploy the changes.

    Note

    Restart the Sync Session after Updating Custom User Data

    The sync server caches custom user data for the duration of the session. As a result, you must restart the sync session after updating custom user data to avoid a compensating write error. To restart the sync session, pause and resume all open Sync Sessions in the client application. For more information on pausing and resuming sync from the client SDKs, see your preferred SDK:

Tip

We need to create a authentication trigger function that creates a custom user object when a user authenticates for the first time. To do so, follow these steps:

  1. Log in to the Realm UI, and then click Triggers in the left hand panel.

  2. Click the Add a Trigger button.

  3. Set the Trigger Type toggle to Authentication.

  4. Under Trigger Details, specify the following values:

    • Name: "onUserCreated"

    • Enbaled: Switch toggle to "On"

    • Action Type: Select "Create"

    • Provider(s): Select "Email/Password", or whichever authentication provider(s) you are using

    • Select an Event Type: Select "Function"

    • Function: Select "+New Function", and then:

      • Function Name: "onUserCreated"

      • Function: Replace the placeholder text with the following function:

      exports = function(authEvent) {
      const user = authEvent.user;
      const collection = context.services.get("mongodb-atlas").db("Item").collection("User");
      const newDoc = {
      _id: user.id,
      email: user.data.email, // Useful for looking up user IDs by email later - assuming email/password auth is used
      team: "", // Used for tiered privileges
      isTeamAdmin: false, // Used for tiered privileges
      isGlobalAdmin: false, // Used for admin privileges
      subscribedTo: [], // Used for restricted feed
      };
      return collection.insertOne(newDoc);
      };

In the custom user data object, create an array that holds the _id values of each author the user is following. In this example, we'll call it "subscriptions". Our user data object looks like the following, where Lily Realmster ("_id": "1234") is subscribed to all documents written by users "456" and "789":

{
"_id" : "1234",
"firstName": "Lily",
"lastName": "Realmster",
"user.custom_data.subscribedTo": [
"456",
"789"
]
}

You can now implement the Restricted Privileges strategy. To do so, follow these general steps:

  1. Log in to the Realm UI, and then click Sync in the left hand panel.

  2. Under Sync Type, choose Flexible.

  3. Set the toggle to enable Development Mode.

  4. Select the cluster you want to sync.

  5. Define a Database Name: select +Add a new database and type a name for the database Realm will use to store your synced objects. You can name it anything you want. A common strategy would be to name the database after the app you're making.

  6. Select Queryable Fields: type in owner_id. This allows your permissions expressions (which you'll set next) to use the any fields called owner_id.

  7. Skip Advanced Configuration, and then click Save Changes to enable Sync.

Now you need to configure the permissions. Navigate to the Rules page. Click the Default Roles and Filters button to edit the default roles. Use the template dropdown to select the template called "Users can only read and write their own data". This populates the rule expression box with the following, which is not exactly what we want, but provides most of the logic for us:

{
"name": "owner-read-write",
"apply_when": {},
"document_filters": {
"read": { "owner_id": "%%user.id" },
"write": { "owner_id": "%%user.id" }
},
"read": true,
"write": true
}

Note that a user can currently read only their own documents ("read": {"owner_id": "%%user.id"}). We can change this to include documents whose authors have IDs in the user's "subscribedTo" array. To do so, we use the $in operator. The expression looks like this:

{
"name": "owner-read-write",
"apply_when": {},
"document_filters": {
"read": {
"owner_id": {
"$in": "%%user.custom_data.subscribedTo"
}
},
"write": { "owner_id": "%%user.id" }
},
"read": true,
"write": true
}

Update the rule expression box with this new logic and save the changes.

Note: we changed the document_filters.read expression to only mention the authors in the subscribedTo array, but users still have "read" access to their own documents because of the document_filters.read expression. Whenever write access is granted, read access is implicitly granted.

We need a function that subscribes one user to another. Our permissions are set up to consider a subscriber "subscribed" to an author if the author's user ID is in the subscriber's custom user data "subscribedTo" array. The "subscribeToUser" function takes a user's email address, finds the corresponding user ID in the custom user data collection, and adds the ID to the requesting user's "subscribedTo" array.

Note that this function will run under System authentication and writes to the custom user data. When using custom user data for permissions, clients must never be allowed to directly edit custom user data. Otherwise, any user could grant themselves any permissions. Instead, modify user data on the backend with a System function. The function should do whatever checks necessary to ensure the user's request for permissions is valid.

To create the function, follow these steps:

  1. Log in to the Realm UI, and then click Functions in the left hand panel.

  2. Click the Create New Function button.

  3. Specify the following values:

    • Name: "subscribeToUser"

    • Authentication: "System"

  4. Switch to the Function Editor tab and replace the placeholder text with the following code:

    exports = async function(email) {
    const collection = context.services.get("mongodb-atlas").db("Item").collection("User");
    // Look up the author object to get the user id from an email
    const author = await collection.findOne({email});
    if (author == null) {
    return {error: `Author ${email} not found`};
    }
    // Whoever called this function is the would-be subscriber
    const subscriber = context.user;
    try {
    return await collection.updateOne(
    {_id: subscriber.id},
    {$addToSet: {
    subscribedTo: author._id,
    }
    });
    } catch (error) {
    return {error: error.toString()};
    }
    };

Note

Template Available!

To use the backend template and get the demo client, run the following command:

appservices apps create --name=add-collaborators --template=flex-sync-guides.add-collaborators

Next, go into the client directory, install the dependencies, and run the demo:

cd add-collaborators/frontend/flex-sync-guides.add-collaborators/
npm install
npm run demo

Read the output on your console to see what the demo is doing.

In the Dynamic Collaboration strategy, users can create documents and add other users as editors of that document.

Like the Read & Write Own Data strategy, this strategy permits a user to create and edit a document if that document's owner_id field equals the user's ID. Additionally, a user may edit the document if the document's collaborators array field contains their ID.

To implement this strategy, follow these general steps:

  1. Log in to the Realm UI, and then click Sync in the left hand panel.

  2. Under Sync Type, choose Flexible.

  3. Set the toggle to enable Development Mode.

  4. Select the cluster you want to sync.

  5. Define a Database Name: select +Add a new database and type a name for the database Realm will use to store your synced objects. You can name it anything you want. A common strategy would be to name the database after the app you're making.

  6. Select Queryable Fields: type in owner_id. This allows your permissions expressions (which you'll set next) to use the any fields called owner_id.

  7. Skip Advanced Configuration, and then click Save Changes to enable Sync.

In the Select Queryable Fields field, type in collaborators as well. This will be the field that stores the IDs of users who may also read and write the document.

Now you need to configure the permissions. Under Define Permissions, use the template dropdown to select the "Custom (start from scratch)". Paste the following into the rule expression box:

{
"name": "collaborator",
"apply_when": {},
"document_filters": {
"read": {
"$or": [
{
"owner_id": "%%user.id"
},
{
"collaborators": "%%user.id"
}
]
},
"write": {
"$or": [
{
"owner_id": "%%user.id"
},
{
"collaborators": "%%user.id"
}
]
}
},
"read": true,
"write": true
}

The "write" and "read" expressions are identical. Take a look at one of them. $or takes an array of options. We have two possible conditions where a user may write the document:

  • The owner_id field of the document equals the user's ID

  • The collaborators array field of the document contains the user's ID.

Generally speaking, when a user is granted write permission, that user automatically gets read permission. However, we can't omit the document_filters.read expression because App Services requires both document_filters.read and document_filters.write to be defined for the role to be Sync compatible.

A user can grant write access on their document to another user by adding that user's ID to the collaborators array field on their document. This can be done on the client side.

We don't recommend using this model for highly sensitive data.

This model uses the permissions system to keep documents private between the document creator and the collaborators they add to the document. However, if a user has write access to a document, they may write to any field of the document. Consequently, this strategy allows collaborators to add other collaborators. It would also allow a collaborator to edit the owner_id field. Field-level permissions have to be boolean literals for sync compatibility, so we can't limit writes to these fields to specific users.

How exactly to get the other user's ID depends on the details of your app. For example, when a user wants to add another user to a document, you might have a search box that accepts an email address. If the given email address corresponds to another user, the client can add that user's ID to the document's collaborators array.

Realm has no built-in way to search users. Generally, the flow for searching users is as follows:

  • Set up an authentication trigger to create a user document when a user registers. The user document contains information you'll use to look up later, such as the user's email address.

  • Create a function that queries the user data collection for a user.

  • Call the function from the client side when the user wants to find another user.

To create an authentication trigger, follow these steps:

  1. Log in to the Realm UI, and then click Triggers in the left hand panel.

  2. Click the Add a Trigger button.

  3. Set the Trigger Type toggle to Authentication.

  4. Under Trigger Details, specify the following values:

    • Name: "onUserCreated"

    • Enbaled: Switch toggle to "On"

    • Action Type: Select "Create"

    • Provider(s): Select "Email/Password", or whichever authentication provider(s) you are using

    • Select an Event Type: Select "Function"

    • Function: Select "+New Function", and then:

      • Function Name: "onUserCreated"

      • Function: Replace the placeholder text with the following function:

      exports = function(authEvent) {
      const user = authEvent.user;
      const collection = context.services.get("mongodb-atlas").db("Item").collection("User");
      const newDoc = {
      _id: user.id,
      email: user.data.email, // Useful for looking up user IDs by email later - assuming email/password auth is used
      team: "", // Used for tiered privileges
      isTeamAdmin: false, // Used for tiered privileges
      isGlobalAdmin: false, // Used for admin privileges
      subscribedTo: [], // Used for restricted feed
      };
      return collection.insertOne(newDoc);
      };

Next, create a system function called findUser:

  • Log in to the Realm UI, go to your Realm app, and then click Functions in the left hand panel.

  • Click the Create New Function button.

  • Provide a descriptive Name for your function.

  • Authentication: Select System. This allows your function to bypass permissions on your collections.

  • Log Function Arguments: Leave it off.

  • Authorization:

    • Can Evaluate: Leave it blank.

    • Private: Leave it off.

  • Click Save. This brings you to the Function Editor, where you can now enter some to run.

Warning

This configuration allows anyone to call this function. As a system function, this function bypasses access rules. Assume any client calling this function has malicious intent.

In the Function Editor, paste the following code and save:

exports = async function(email) {
const collection = context.services.get("mongodb-atlas")
.db("Item").collection("User");
const filter = {
email,
};
// Search for the user by email
const result = await collection.findOne(filter);
// Return corresponding user id or null
return result != null ? result._id : null;
};

From your client, you can now call this function. The function's only argument is an email address string. If the email corresponds to a user, the function returns the user's ID. Otherwise, it returns null.

Note

Template Available!

To use the backend template and get the demo client, run the following command:

appservices apps create --name=tiered --template=flex-sync-guides.tiered

Custom User Data Configuration Bug Workaround: Sometimes, the template generator does not copy the custom user data configuration to the new app correctly. You can fix this as follows: the appservices apps create command should have output some JSON about the app you just created. From this JSON, copy the "url" value (something like https://services.cloud.mongodb.com/groups/...) and visit that URL in your browser. Log in if prompted. From the app dashboard, in the left-hand panel, click App Users. Click Custom User Data. Ensure Enable Custom User Data is ON. If it was not on, turn it on and enter "mongodb-atlas", "Item", and "User" for Cluster Name, Database Name, and Collection Name, respectively. For User ID Field, enter _id. Hit Save (or Save Draft, then deploy).

Next, in your terminal, go into the client directory, install the dependencies, and run the demo:

cd tiered/frontend/flex-sync-guides.tiered/
npm install
npm run demo

Read the output on your console to see what the demo is doing.

In this permission strategy, we'll add special roles as well as rules. There are two roles: a team member and a team administrator. The rules are as follows:

  • Each user is a member of a team.

  • A user can read and write their own documents.

  • All members of the team can read all documents created by team members.

  • Each team has a team administrator, who has read & write permissions on every team document.

To make this work, we need to do the following:

  • Enable custom user data

  • Create a trigger function to create a new custom user data object for each new user

  • Create a function to add a user to a team

  • Define the permissions

Note

When using custom user data for permissions, never allow the client to write the custom user data object. Doing so would allow any user to grant themselves any permission. Instead, use system functions on the server side to update the custom user data object.

To enable Custom User Data in the App Services UI, follow these steps:

  1. Click App Users in the left hand panel.

  2. Select the User Settings tab and find the Custom User Data section.

  3. Set the Enable Custom User Data toggle to On.

  4. Specify the following values:

    • Cluster Name: The name of a linked MongoDB cluster that will contain the custom user data database.

    • Database Name: The name of the MongoDB database that will contain the custom user data collection.

    • Collection Name: The name of the MongoDB collection that will contain custom user data.

  5. Specify the User ID field. Every document in the custom user data collection must have a field that maps to a specific user. The field must be present in every document that maps to a user, and must contain the user's ID as a string. We recommend that you use the standard _id field to store the user ID. MongoDB automatically places a constraint on the _id field, ensuring uniqueness.

    Note

    If two documents in this collection contain the same user ID value, App Services uses the first document that matches, which leads to unexpected results.

  6. Save and deploy the changes.

    Note

    Restart the Sync Session after Updating Custom User Data

    The sync server caches custom user data for the duration of the session. As a result, you must restart the sync session after updating custom user data to avoid a compensating write error. To restart the sync session, pause and resume all open Sync Sessions in the client application. For more information on pausing and resuming sync from the client SDKs, see your preferred SDK:

We need to create a authentication trigger function that creates a custom user object when a user authenticates for the first time. To do so, follow these steps:

  1. Log in to the Realm UI, and then click Triggers in the left hand panel.

  2. Click the Add a Trigger button.

  3. Set the Trigger Type toggle to Authentication.

  4. Under Trigger Details, specify the following values:

    • Name: "onUserCreated"

    • Enbaled: Switch toggle to "On"

    • Action Type: Select "Create"

    • Provider(s): Select "Email/Password", or whichever authentication provider(s) you are using

    • Select an Event Type: Select "Function"

    • Function: Select "+New Function", and then:

      • Function Name: "onUserCreated"

      • Function: Replace the placeholder text with the following function:

      exports = function(authEvent) {
      const user = authEvent.user;
      const collection = context.services.get("mongodb-atlas").db("Item").collection("User");
      const newDoc = {
      _id: user.id,
      email: user.data.email, // Useful for looking up user IDs by email later - assuming email/password auth is used
      team: "", // Used for tiered privileges
      isTeamAdmin: false, // Used for tiered privileges
      isGlobalAdmin: false, // Used for admin privileges
      subscribedTo: [], // Used for restricted feed
      };
      return collection.insertOne(newDoc);
      };

We now need a function that adds a user to a team. Note that this function will run under System authentication and writes to the custom user data. It does not perform an upsert because the user custom data was created when the user successfully authenticated for the first time.

To create the function, follow these steps:

  1. Log in to the Realm UI, and then click Functions in the left hand panel.

  2. Click the Create New Function button.

  3. Specify the following values:

    • Name: "joinTeam"

    • Authentication: "System"

  4. Switch to the Function Editor tab and replace the placeholder text with the following code:

    exports = async function(userId, teamName) {
    const collection = context.services.get("mongodb-atlas")
    .db("Item").collection("User");
    const filter = { _id: userId };
    const update = { $set: { team: teamName }};
    const options = { upsert: false };
    return collection.updateOne(filter, update, options);
    };

The following permissions specify two roles:

  • teamAdmin applies only when the user's custom data has isTeamAdmin: true. If so, the user can read and write all documents where the document's team value matches the user's team value.

  • teamMember applies to every user. The user can write their own documents and read all documents where the document's team value matches the user's team value.

[
{
"name": "admin",
"apply_when": {
"%%user.custom_data.isTeamAdmin": true
},
"document_filter": {
"read": {
"team": "%%user.custom_data.team"
},
"write": {
"team": "%%user.custom_data.team"
}
},
"read": true,
"write": true
},
{
"name": "user",
"apply_when": {},
"document_filters": {
"read": {
"team": "%%user.custom_data.team"
},
"write": {
"owner_id": "%%user.id"
}
},
"read": true,
"write": true
}
]

Note

Take It Further

This strategy can be expanded to support a "globalAdmin" role. The global admin would have read & read permissions on any doc created in any team.

Tip

See also: Authentication Triggers

←  App Builder's GuideSync Data in Atlas with a Client Application →