DEV Community

Cover image for The MongoDB Role Pitfalls That Bit Me: Lessons in Multi-Customer Access Control
Hemanth Babu
Hemanth Babu

Posted on

The MongoDB Role Pitfalls That Bit Me: Lessons in Multi-Customer Access Control

The MongoDB Role Pitfalls That Bit Me: Lessons in Multi-Customer Access Control

TL;DR:

  • ❌ Global roles don't auto-scope to each customer databases
  • 🎯 Create roles per customer database
  • 🔐 Define privileges exactly where they apply
  • 🤖 Automate role/user creation for scale
  • 🔄 Always restart after config changes

🔍 Intro: "I thought I understood MongoDB access control... until I didn't."

I'd just published a guide on setting up MongoDB users and custom roles. See the tutorial-style primer on Dev.to version or Medium version. It covered the commands, the theory, and a neat walkthrough.

Confidently, I thought: “Great—now I can lock down any MongoDB instance.” But when I tried to adapt that for a multi-customer, multi-module SaaS environment, things fell apart in unexpected ways.

In this post, I'll share exactly what I tried, why it didn't work, and how I fixed it. If you're building a MongoDB setup where each customer has its own database and each module/service should only see its own collections, these lessons will save you headaches. And if you haven't read the tutorial-style primer yet, check it out here:


🏗️ What I Was Trying to Build

Diagram: What I Was Trying to Build

  • Architecture in simple terms:

    • A SaaS-like application where each customer has its own database.
    • Within each customer database, multiple modules/services (e.g., orders, products, analytics) each have dedicated collections.
    • Each module (i.e., the code/service) uses a MongoDB user to access only its own collections for that specific customer.
  • End goal: When Service A runs for Customer X, it connects with credentials that allow only the “CustomerX*ServiceA” collections. It must _never* access ServiceB collections or CustomerY data.

  • Initial idea: Create roles at the admin level—one role per module/service that spans all customer databases. Then, for each customer, assign that role scoped to the customer database. I assumed that specifying { role: "<moduleRole>", db: "<customerDb>" } in db.createUser() would filter the module role's privileges to that customer database.

In short: I thought I could define one “ServiceA” role globally, then assign it with db= to restrict it to that DB. Spoiler: that assumption was wrong.


⚠️ The Misunderstanding: “db” in createUser Is Where the Role Lives, Not Its Scope Filter

Initial Plan (Wrong assumption)

// Pseudocode for each module role created once under admin database:
use admin;
db.createRole({
  role: "serviceA",
  privileges: [
    // Suppose we try to list resource patterns under many DBs:
    { resource: { db: "customer1_db", collection: "serviceA_*" }, actions: ["find", "insert", "update", "remove"] },
    { resource: { db: "customer2_db", collection: "serviceA_*" }, actions: ["find", "insert", "update", "remove"] },
    // ...and so on for each customer
  ],
  roles: []
});
Enter fullscreen mode Exit fullscreen mode

Then for user creation:

use customerX_db;
db.createUser({
  user: "customerX_serviceA_user",
  pwd: passwordPrompt(),
  roles: [
    { role: "serviceA", db: "customerX_db" }
  ]
});
Enter fullscreen mode Exit fullscreen mode

I assumed:

“By specifying db: "customerX_db", the serviceA role's privileges get filtered to customerX_db only.”

Reality: Role's db parameter is where the role is defined, not a filter of where it applies.

  • The { role: "...", db: "customerX_db" } in createUser means “look for a role named ‘serviceA' in the customerX_db database.” It does not say “apply the global serviceA role only in this DB.”
  • If “serviceA” role was created under admin (or any other DB), referencing it under customerX_db fails (role not found), or if found under another DB, privileges don't magically filter to the user's connect-DB context.
  • MongoDB's RBAC: A role's actions/resources are defined exactly where you create the role. Assigning that role to a user requires you refer to that same DB where the role exists.

Lesson: You cannot “create once globally and assign with filtering by specifying db in createUser.” The db is the namespace of the role definition.


🔄 How I Fixed It: Role Creation Per Customer Database

Approach:

  1. For each customer database, create distinct roles for each module/service.
  2. When creating the user for a module in that customer DB, reference the role in the same DB.

Example Flow (pseudo-automation script style):

// For each customer (e.g., customerX_db):
use customerX_db;

// 1. Create or update role for ServiceA in this customer DB:
const roleName = "serviceA"; // consider prefixing with module name or include customer in name if desired
db.createRole({
  role: roleName,
  privileges: [
    {
      resource: { db: "customerX_db", collection: "serviceA_orders" },
      actions: ["find", "insert", "update", "remove"]
    },
    // Note: MongoDB privileges require exact collection names; wildcards aren't supported here, so list each collection explicitly.

  ],
  roles: []
});

// 2. Create user for ServiceA in this customer DB:
db.createUser({
  user: "customerX_serviceA_user",
  pwd: passwordPrompt(),
  roles: [
    { role: roleName, db: "customerX_db" }
  ]
});
Enter fullscreen mode Exit fullscreen mode
  • You repeat this for ServiceB, ServiceC, etc., in each customer DB.
  • If you have many customers, script this process (e.g., a Node.js or Bash script that loops customer list).

Why This Works:

  • Role is defined in the same database where its privileges apply, so no confusion.
  • When the service connects with mongodb://customerX_serviceA_user:pwd@host:27017/customerX_db?authSource=customerX_db, it gets only the privileges defined in that DB's role.

Tip: If you have common privilege patterns per module, template the JSON structure and programmatically fill in the db and collection names per customer.


🔧 Bonus Pitfall: Enabling Authentication in mongod.cfg

  • What happened: After editing mongod.cfg to add:
  # In mongod.cfg, ensure 'security:' is at root indentation level; YAML is indentation-sensitive.

  security:
    authorization: enabled
Enter fullscreen mode Exit fullscreen mode

I tried connecting with the new users, but MongoDB still allowed unauthenticated access.

  • Root cause: Didn't restart the MongoDB service/process after config change.
  • Fix: Always restart or reload the mongod service after modifying the config file.

    • On Windows, use Services UI or net stop MongoDB, net start MongoDB (or the equivalent PowerShell commands).
    • On Linux, sudo systemctl restart mongod.
  • Lesson: Authentication “not working” is often because the config change isn't live yet.


📈 What I Learned (and You Should Know)

  1. Understand that the db field specifies where roles live, not their access scope.
    • The db field in createRole and in user's roles assignment refers to where the role is defined, not a dynamic filter. Always create roles in the DB where they should apply.
  2. Multi-customer (multi-DB) adds complexity
    • One-size-fits-all global roles often don't work. Better to script role/user creation per customer DB.
  3. Automate repetitive setup
    • If you have tens or hundreds of customers, manually creating roles/users is error-prone. Build scripts or use Infrastructure-as-Code.
  4. Test early and often
    • Spin up a staging customer database, create roles/users, verify permissions before rolling out widely.
  5. Restart after config changes
    • Simple but easily overlooked; always confirm authorization: enabled is in effect.

✅ Conclusion & Next Steps

  • Key takeaway: Don't assume MongoDB will scope a global role by specifying a different db at user-creation time. Define roles in the exact database where their privileges apply. (For the basic tutorial on custom roles, see My previous MongoDB ACL tutorial on Dev.to.)
  • Call to action: Have you tackled MongoDB RBAC in a multi-DB environment? What strategies or scripts did you use? Share your approaches or questions in the comments below!

Top comments (0)