MongoDB · Multi-Tenancy

Row-Level Security and Multi-Tenancy on MongoDB

How Back4app Solves the Hard Parts

B4A
Back4app Team
Engineering · May 29, 2026 · 8 min read

If you've built anything multi-tenant on MongoDB, you already know the pitch by heart. Flexible schemas, horizontal scaling, per-tenant collections or a shared collection with a tenantId field — pick your flavor, ship your MVP, sleep well at night.

Until the day someone writes an aggregation pipeline and forgets the $match: { tenantId } stage.

That moment — the cold sweat at 11:47 PM when you realize a customer query just returned another customer's invoices — is the part nobody puts in the pitch deck. Multi-tenancy on MongoDB isn't a storage problem—MongoDB handles flexible indexing, sharding, and raw data footprints flawlessly. It's an access-control challenge. Because MongoDB is designed to be a pure, high-performance, and unopinionated document store, it intentionally leaves application-level authorization to the data-access layer.

This article is about how to solve it properly: by adding a row-level security layer on top of your existing MongoDB Atlas cluster, in minutes, without migrating a byte of data.

TL;DR

  • MongoDB focuses entirely on high-performance document flexibility, meaning row-level security (RLS) is traditionally managed at the application layer.
  • Back4app sits on top of your existing Atlas cluster and gives you three security primitives out of the box: Application Keys, Class-Level Permissions (CLP), and Access Control Lists (ACL).
  • You connect Atlas by pasting a mongodb+srv://... connection string. Your data stays in your cluster, in your account, under your backup policy.
  • Multi-tenancy becomes a data-layer concern instead of a per-endpoint concern. Worked example below in about 40 lines.
01 · THE PROBLEM

The Three Multi-Tenant Patterns in MongoDB, and Where Each One Breaks

There are essentially three ways teams structure multi-tenant data in MongoDB. None of them solve access control — they just decide where the access control problem will hurt.

  • Database-per-tenant gives you the cleanest isolation. It's beautiful at five tenants and miserable at five hundred. Connection pooling, migrations, monitoring, and backups all multiply by your tenant count.
  • Collection-per-tenant is the middle path. Easier to operate, but you trade index sprawl for isolation. Every new collection is another schema to keep in sync.
  • Shared collection with a discriminator field is the cheapest and the riskiest. Everything lives in one projects collection with a tenantId, and every single query needs to filter on it. The aggregation pipeline. The CSV export script. The intern's analytics dashboard. Miss one filter and you've got a breach.

The underlying question is the same across all three: how do you guarantee that a request only ever returns the rows the caller is allowed to see? That's row-level security. Because MongoDB remains elegantly unopinionated about your application's specific tenancy rules, this fine-grained access security is best managed right at the API and data-governance layer.

Multi-tenancy on MongoDB isn't a storage problem. It's an access-control challenge.

02 · DEFINITION

What “Row-Level Security” Actually Means in a Document Store

If you've worked with PostgreSQL, you know RLS as a policy attached to a table. Postgres evaluates the policy on every query, and the rule lives next to the data. You can't forget to apply it, because there's no separate place to apply it from.

MongoDB takes a different, highly flexible approach. It optimizes for raw query performance and document polymorphism, leaving access policies to upstream services. Because of this architectural freedom, teams building multi-tenant apps on MongoDB typically implement a data-access layer to govern incoming requests.

The one catch with a hand-rolled data-access layer is consistency — it has to be applied across every endpoint, background job, and new developer, since the rule lives in application code rather than in the data itself.

The fix is to move the rule into the data layer. That's what Back4app does.

03 · THE MODEL

Back4app: Security as a Data-Layer Primitive

Back4app is a managed backend platform that runs on top of MongoDB. It gives you a REST and SDK API, user authentication, Cloud Code for server-side logic, and a three-layer security model that wraps every read and write before it touches your collection.

The three layers, all configured in the Back4app dashboard:

1

Application Keys — who is the app talking to the backend? The outer perimeter.

2

Class-Level Permissions (CLP) — what can a role do to this collection? Default-deny at the schema level.

3

Access Control Lists (ACL) — who can do what to this specific document? The row-level layer, attached to every object.

All three are enforced by Back4app before the query reaches MongoDB. You don't write the filters. You can't forget the filters. The filters live with the data.

The filters live with the data.

04 · SETUP

Bring Your Own MongoDB: Connecting an Atlas Cluster

Before going deeper, the part that matters most for teams already running on Atlas: you don't have to migrate. Back4app can sit on top of the cluster you already have.

The setup takes about five minutes:

  1. 1

    Grab your Atlas connection string. In the Atlas console, click Connect on your cluster and copy the mongodb+srv://... URI. Make sure the user in that URI has read/write access to the database you want Back4app to use.

  2. 2

    Whitelist Back4app's outbound IPs in Atlas Network Access. Atlas blocks all connections by default. Add the Back4app egress IPs (listed in the Back4app docs) to the IP Access List, or — if your security posture allows it — temporarily open 0.0.0.0/0 while you verify the connection.

  3. 3

    Paste the connection string into Back4app. From the Back4app dashboard, open Database Hub → Connect existing MongoDB and paste the URI.

That's the entire migration. Back4app provisions the API and security layer on top of your cluster. The _User, _Role, _rperm, and _wperm system fields land in collections inside the database you own. Your backup policy, your VPC peering, your billing, your point-in-time recovery — all unchanged.

What Back4app manages: the API, authentication, the security enforcement layer, Cloud Code execution. What you keep owning: the data, the cluster, the indexes, the operational story around them.

05 · LAYER 1

Application Keys: The Outer Perimeter

Application Keys are how Back4app knows which app is talking to it and what that app is allowed to do.

The Client Keys and the JavaScript Key are safe to ship in client bundles. They identify the app but carry no elevated privileges — every request is still subject to CLP and ACL enforcement. The REST API Key is for server-to-server calls. The Master Key bypasses everything — CLP, ACL, all of it. It's how trusted server code and Cloud Code perform administrative operations, and it must never appear in a client bundle, public repo, or frontend environment variable.

For multi-tenancy, you typically have one set of keys per environment (dev, staging, prod) rather than per tenant. Tenancy is enforced at the CLP and ACL layers, not the key layer.

06 · LAYER 2

Class-Level Permissions: Default-Deny on the Collection

CLP is the schema-level lock. It applies to an entire class (collection) and answers questions like: can anonymous users read this? Can authenticated users write? Can any client delete via REST?

The right starting posture for a multi-tenant collection is to deny everything you don't explicitly need. For an Invoice collection, that might look like:

  • Get / Find: requires authentication; no public access.
  • Create: restricted to role:Admin and role:Billing.
  • Update: restricted to role:Admin.
  • Delete: disabled entirely via REST. Deletes happen in Cloud Code with the Master Key, behind an audit log.

CLP is necessary but not sufficient for multi-tenancy. It tells Back4app that some authenticated user can read invoices. It doesn't tell Back4app which invoices a given user is allowed to see. That's where ACLs come in.

07 · LAYER 3

ACLs: True Row-Level Security on MongoDB

Every object in Back4app carries an ACL. The ACL is stored as a first-class field on the document and answers the question: for this specific row, who can read it and who can write it?

ACLs come in three flavors:

Public ACLs grant access to everyone. Useful for things like a public marketing-site CMS.

User-specific ACLs grant access to a single user by ID. Useful for personal data — a user's profile, their notifications.

Role-based ACLs grant access to anyone who holds a given role. This is the lever that makes multi-tenancy click.

The pattern that turns this into row-level security for multi-tenancy is simple: create one role per tenant (tenant_abc123), assign the tenant's users to that role, and give every document the tenant created an ACL that grants read and write access to that role only.

A request from a user in tenant_abc123 will only ever return documents whose _rperm includes role:tenant_abc123. A request from a user in tenant_xyz789 will get an empty result set on the same query — not because you filtered, but because Back4app refused to return rows the caller can't see. The rule lives with the data.

08 · IN PRACTICE

Putting It Together: A Multi-Tenant SaaS in About 40 Lines

Here's the centerpiece. A Cloud Code beforeSave hook that runs every time someone creates or updates a Project. On insert, it stamps the new document with an ACL scoped to the user's tenant role.

Cloud Code — beforeSave hook
Parse.Cloud.beforeSave("Project", async (request) => {
  const { object, user, master } = request;
  if (!user && !master) {
    throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, "Unauthorized");
  }
  if (object.isNew() && user) {
    const tenantRoleName = user.get("tenantRoleName"); // e.g., "tenant_abc123"
    if (!tenantRoleName) {
      throw new Parse.Error(
        Parse.Error.VALIDATION_ERROR,
        "User missing tenant context."
      );
    }
    const acl = new Parse.ACL();
    acl.setRoleReadAccess(tenantRoleName, true);
    acl.setRoleWriteAccess(tenantRoleName, true);
    object.setACL(acl);
  }
});

That's the entire row-level-security implementation for the Project collection. Repeat the same pattern for Task, Comment, Invoice, or any other tenant-owned class — typically with a tiny shared helper so you're not duplicating the ACL construction.

The client-side code that creates a project looks like every other Back4app save:

Client — JavaScript SDK
const Project = Parse.Object.extend("Project");
const project = new Project();
project.set("name", "Q3 Launch");
await project.save();

No tenantId field. No where: { tenantId } filter on the query. No middleware. The rule lives with the data, enforced by Back4app before MongoDB ever sees the query. You no longer write where: { tenantId } on every client query — and you no longer worry about the day someone forgets to.

09 · BEYOND THE HAPPY PATH

Edge Cases Worth Knowing About

A few things you'll run into once you're past the happy path.

Master Key bypass for aggregations. Reporting queries, admin tools, and cross-tenant analytics need to ignore ACLs by design. From Cloud Code, you do this explicitly with { useMasterKey: true }. Keep it visible in the code and gate it behind an admin role check so the bypass itself is access-controlled.

Cross-tenant admin views. Your support staff needs to see data across tenants. The clean pattern is a support role with read access added to every tenant ACL at write time. Resist the temptation to hand support engineers the Master Key.

Documents shared across tenants. ACLs compose. A document can grant read access to two different tenant roles by calling setRoleReadAccess twice; the _rperm array simply contains both role names.

Indexing _rperm and _wperm on Atlas. Once your collections get large, a multikey index on _rperm (and _wperm for write-heavy collections) keeps role-filtered reads cheap. Run explain() to confirm the planner is using it.

10 · THE FIT

When Back4app Is the Right Call

Back4app is the ideal companion layer when you want to pair MongoDB's legendary scaling and flexibility with turnkey, enterprise-grade access control. It allows your development team to focus on building features rather than maintaining custom security middleware on top of your database.

It shines brightest for multi-tenant SaaS, B2B platforms, and mobile backends — anywhere per-tenant access control is core to the product. For a single-tenant app or a pure-analytics workload, you may simply not need this layer yet, and that's perfectly fine.

11 · FAQ

Frequently Asked Questions

Does MongoDB support row-level security?
MongoDB provides incredibly robust security features at the database level, including Role-Based Access Control (RBAC) for database administrators, end-to-end encryption, and network isolation via MongoDB Atlas. For document-level application security (like isolating SaaS tenants), MongoDB intentionally leaves that to the application architecture. Back4app acts as that native extension, managing per-document permissions flawlessly on top of your Atlas collections.
Do I have to migrate my data off MongoDB Atlas to use Back4app?
No. Back4app connects to your existing Atlas cluster — you paste your mongodb+srv://... connection string into the Database Hub and whitelist Back4app's outbound IPs in Atlas Network Access. Your data stays in your cluster, in your account, under your backup, VPC peering, and point-in-time recovery policies. Back4app provisions the API and security layer on top.
How do ACLs implement multi-tenancy?
Create one role per tenant (for example tenant_abc123), assign the tenant's users to that role, and give every document the tenant creates an ACL that grants read/write access to that role only. A request from a user in tenant_abc123 only ever returns documents whose _rperm includes role:tenant_abc123; a user in another tenant gets an empty result set on the same query — not because you filtered, but because Back4app refused to return rows the caller can't see.
What's the difference between CLP and ACL?
Class-Level Permissions (CLP) are a schema-level lock on an entire collection — they answer "can authenticated users read invoices at all?" Access Control Lists (ACL) are per-document — they answer "which invoices can this specific user see?" CLP is necessary but not sufficient for multi-tenancy; ACLs provide the actual row-level isolation.
When is Back4app not the right fit?
It is a less natural fit for purely analytical workloads (where you mostly want full-cluster reads), single-tenant apps with trivial access rules, or systems with a deeply custom auth model that does not map cleanly to users and roles. Back4app also brings an opinionated schema — you inherit the _User and _Role system classes and run server-side logic in Cloud Code. For most multi-tenant apps that is a great trade; be honest about whether you are one of the exceptions.
GET STARTED

Get Started

If you're already on Atlas, the fastest way to feel this is to point Back4app at a non-production cluster and try the beforeSave hook above on a throwaway class. Create a Back4app app, paste your mongodb+srv://... connection string into Database Hub, and you'll have App Keys, CLP, and ACLs wired up on your existing cluster in minutes — no data migration, no second source of truth.

Multi-tenancy on MongoDB doesn't have to be a per-endpoint anxiety. Push the rule down to where the data lives, and the whole class of cross-tenant bugs disappears.

Try it on your own Atlas cluster

Free tier · no credit card · no data migration