MongoDB
Authorization & Roles
Authentication answers the question who are you? — authorization answers what are you allowed to do? MongoDB's authorization system is built on roles. A role is a named collection of privileges, where each privilege grants specific actions on a specific resource. Every user is assigned one or more roles, and the union of all their role privileges determines exactly what that user can and cannot do. MongoDB ships with a comprehensive set of built-in roles covering common use cases, and allows you to create custom roles with surgical precision — down to individual collections and individual action types. This lesson covers every built-in role, how to create custom roles, how to apply collection-level and field-level restrictions, and how to use the $redact aggregation stage to enforce field-level access control in query results.
1. Built-in Roles — The Full Reference
MongoDB's built-in roles are grouped into four categories: database user roles for everyday read/write access, database admin roles for schema and index management, cluster roles for replica set and sharding management, and superuser roles for full administrative access. Knowing which built-in role fits each use case prevents the common mistake of assigning admin-level access to application users.
# Built-in roles — complete reference with Dataplexa use cases
from pymongo import MongoClient
client = MongoClient(
"mongodb://dataplexa_admin:StrongP@ssword123!@localhost:27017/",
authSource="admin"
)
admin_db = client["admin"]
# ── DATABASE USER ROLES ───────────────────────────────────────────────────
print("Database User Roles (assign per database):\n")
user_roles = [
("read", "Find documents, list collections, run aggregations. No writes."),
("readWrite", "All of read + insert, update, delete, createIndex, dropIndex."),
]
for role, desc in user_roles:
print(f" {role:20} {desc}")
# ── DATABASE ADMIN ROLES ──────────────────────────────────────────────────
print("\nDatabase Administration Roles:\n")
admin_roles = [
("dbAdmin", "Manage indexes, view stats, run explain. No read/write data."),
("dbOwner", "Combines readWrite + dbAdmin + userAdmin. Full DB control."),
("userAdmin", "Create, modify, and delete users and roles in one database."),
]
for role, desc in admin_roles:
print(f" {role:20} {desc}")
# ── CLUSTER ADMIN ROLES ───────────────────────────────────────────────────
print("\nCluster Administration Roles (apply to admin database):\n")
cluster_roles = [
("clusterMonitor", "Read-only access to cluster metrics and replica set status."),
("hostManager", "Monitor and manage individual mongod/mongos processes."),
("clusterManager", "Manage sharding and replication. Includes clusterMonitor."),
("clusterAdmin", "Full cluster management. Includes all cluster roles + dropDatabase."),
]
for role, desc in cluster_roles:
print(f" {role:20} {desc}")
# ── SUPERUSER ROLES ───────────────────────────────────────────────────────
print("\nSuperuser Roles (use sparingly):\n")
super_roles = [
("readAnyDatabase", "read on every database except local and config."),
("readWriteAnyDatabase", "readWrite on every database except local and config."),
("userAdminAnyDatabase", "userAdmin on every database — can grant any privilege."),
("dbAdminAnyDatabase", "dbAdmin on every database."),
("root", "All privileges on all resources. Complete superuser."),
]
for role, desc in super_roles:
flag = "⚠" if role == "root" else "○"
print(f" {flag} {role:28} {desc}")
# Demonstrate: show roles for the current users
print("\nCurrent user role assignments in dataplexa:\n")
users = admin_db.command("usersInfo", 1).get("users", [])
for u in users:
roles = [f"{r['role']}@{r['db']}" for r in u.get("roles", [])]
print(f" {u['user']:18} {', '.join(roles)}")read Find documents, list collections, run aggregations. No writes.
readWrite All of read + insert, update, delete, createIndex, dropIndex.
Database Administration Roles:
dbAdmin Manage indexes, view stats, run explain. No read/write data.
dbOwner Combines readWrite + dbAdmin + userAdmin. Full DB control.
userAdmin Create, modify, and delete users and roles in one database.
Cluster Administration Roles:
clusterMonitor Read-only access to cluster metrics and replica set status.
hostManager Monitor and manage individual mongod/mongos processes.
clusterManager Manage sharding and replication.
clusterAdmin Full cluster management including dropDatabase.
Superuser Roles:
○ readAnyDatabase read on every database except local and config.
○ readWriteAnyDatabase readWrite on every database except local and config.
○ userAdminAnyDatabase userAdmin on every database — can grant any privilege.
○ dbAdminAnyDatabase dbAdmin on every database.
⚠ root All privileges on all resources. Complete superuser.
Current user role assignments:
dataplexa_admin userAdminAnyDatabase@admin, readWriteAnyDatabase@admin, clusterAdmin@admin
app_user readWrite@dataplexa
report_user read@dataplexa
monitor_user clusterMonitor@admin, read@local
- Never assign the
rootrole to an application user — it grants every privilege on every resource including dropping databases and modifying other users' credentials dbAdminandreadWriteare complementary — a developer account often needs both:readWritefor data access anddbAdminfor index management, without needingdbOwnerwhich adds unnecessaryuserAdminprivilegesuserAdminis a powerful role even withoutreadWrite— a user withuserAdmincan grant themselves any privilege, so treat it as a superuser role in practice
2. Custom Roles — Fine-Grained Collection-Level Privileges
Built-in roles operate at the database level — readWrite grants access to every collection in a database. Custom roles let you define privileges at the collection level — a role that can read products and write reviews but cannot touch orders or users at all. This is essential for multi-tenant applications and microservices where each service should see only its own data.
# Custom roles — collection-level and action-level privilege control
from pymongo import MongoClient
client = MongoClient(
"mongodb://dataplexa_admin:StrongP@ssword123!@localhost:27017/",
authSource="admin"
)
admin_db = client["admin"]
# ── Custom role 1: catalogue service ─────────────────────────────────────
# Can read and write products — nothing else
admin_db.command("createRole", "catalogueService",
privileges=[
{
"resource": {"db": "dataplexa", "collection": "products"},
"actions": ["find", "insert", "update", "remove",
"createIndex", "dropIndex"]
},
{
"resource": {"db": "dataplexa", "collection": "reviews"},
"actions": ["find"] # read reviews to display alongside products
},
],
roles=[] # no inherited roles
)
print("Created role: catalogueService")
print(" ✓ find/insert/update/remove on products")
print(" ✓ find on reviews")
print(" ✗ no access to orders, users, or any other collection\n")
# ── Custom role 2: order service ──────────────────────────────────────────
# Can read users (to validate), write orders, read/write order_items
admin_db.command("createRole", "orderService",
privileges=[
{
"resource": {"db": "dataplexa", "collection": "users"},
"actions": ["find"] # read-only — validate user exists
},
{
"resource": {"db": "dataplexa", "collection": "products"},
"actions": ["find", "update"] # read + update stock only
},
{
"resource": {"db": "dataplexa", "collection": "orders"},
"actions": ["find", "insert", "update"]
},
{
"resource": {"db": "dataplexa", "collection": "order_items"},
"actions": ["find", "insert", "update", "remove"]
},
],
roles=[]
)
print("Created role: orderService")
print(" ✓ find on users (validate customer)")
print(" ✓ find + update on products (check/decrement stock)")
print(" ✓ find/insert/update on orders")
print(" ✓ full access to order_items")
print(" ✗ cannot delete orders or touch reviews\n")
# ── Custom role 3: analytics reader ──────────────────────────────────────
# Read-only across specific collections — for BI tools
admin_db.command("createRole", "analyticsReader",
privileges=[
{
"resource": {"db": "dataplexa", "collection": "orders"},
"actions": ["find", "aggregate"]
},
{
"resource": {"db": "dataplexa", "collection": "order_items"},
"actions": ["find", "aggregate"]
},
{
"resource": {"db": "dataplexa", "collection": "products"},
"actions": ["find", "aggregate"]
},
],
roles=[]
)
print("Created role: analyticsReader")
print(" ✓ find + aggregate on orders, order_items, products")
print(" ✗ no access to users (PII protection)")
print(" ✗ no write access anywhere\n")
# Assign custom roles to users
admin_db.command("createUser", "catalogue_svc",
pwd="CatSvc@Pass111!",
roles=[{"role": "catalogueService", "db": "admin"}]
)
admin_db.command("createUser", "order_svc",
pwd="OrdSvc@Pass222!",
roles=[{"role": "orderService", "db": "admin"}]
)
print("Users created with custom roles:")
print(" catalogue_svc → catalogueService")
print(" order_svc → orderService")✓ find/insert/update/remove on products
✓ find on reviews
✗ no access to orders, users, or any other collection
Created role: orderService
✓ find on users (validate customer)
✓ find + update on products (check/decrement stock)
✓ find/insert/update on orders
✓ full access to order_items
✗ cannot delete orders or touch reviews
Created role: analyticsReader
✓ find + aggregate on orders, order_items, products
✗ no access to users (PII protection)
✗ no write access anywhere
Users created with custom roles:
catalogue_svc → catalogueService
order_svc → orderService
- Custom role resources specify both
dbandcollection— a role can be precisely scoped to a single collection in a single database, making it impossible for that role to accidentally access anything else - Available actions include:
find,insert,update,remove,aggregate,createIndex,dropIndex,createCollection,dropCollection,listCollections,listIndexes, and more — you can allow only the specific operations each service actually performs - Separating the analytics user from users collection is a PII protection pattern — analytics queries on orders and products never need to join to real customer names or emails
3. Managing Roles — Update, Grant, Revoke
Roles and user assignments change over time as applications evolve. MongoDB provides commands to update role privileges, grant additional roles to existing users, and revoke roles that are no longer needed — without recreating users or disrupting active sessions.
# Managing roles — update, grant, revoke, inspect
from pymongo import MongoClient
client = MongoClient(
"mongodb://dataplexa_admin:StrongP@ssword123!@localhost:27017/",
authSource="admin"
)
admin_db = client["admin"]
# ── Update a role — add a new privilege ───────────────────────────────────
# analyticsReader now also needs access to reviews for sentiment analysis
admin_db.command("updateRole", "analyticsReader",
privileges=[
{
"resource": {"db": "dataplexa", "collection": "orders"},
"actions": ["find", "aggregate"]
},
{
"resource": {"db": "dataplexa", "collection": "order_items"},
"actions": ["find", "aggregate"]
},
{
"resource": {"db": "dataplexa", "collection": "products"},
"actions": ["find", "aggregate"]
},
{
"resource": {"db": "dataplexa", "collection": "reviews"},
"actions": ["find", "aggregate"] # newly added
},
],
roles=[]
)
print("analyticsReader updated — reviews access added\n")
# ── Grant an additional role to an existing user ──────────────────────────
# report_user now also needs the analyticsReader role
admin_db.command("grantRolesToUser", "report_user",
roles=[{"role": "analyticsReader", "db": "admin"}]
)
print("Granted analyticsReader to report_user\n")
# ── Revoke a role from a user ─────────────────────────────────────────────
# report_user's read role on dataplexa is now superseded by analyticsReader
admin_db.command("revokeRolesFromUser", "report_user",
roles=[{"role": "read", "db": "dataplexa"}]
)
print("Revoked read@dataplexa from report_user\n")
# ── Inspect a specific role's privileges ─────────────────────────────────
role_info = admin_db.command("rolesInfo",
{"role": "analyticsReader", "db": "admin"},
showPrivileges=True
)
print("analyticsReader privileges after update:")
for role_doc in role_info.get("roles", []):
for priv in role_doc.get("privileges", []):
resource = priv.get("resource", {})
actions = priv.get("actions", [])
coll = resource.get("collection", "*")
print(f" {coll:15} {', '.join(sorted(actions))}")
# ── Inspect a user's current roles ───────────────────────────────────────
print("\nreport_user current roles:")
user_info = admin_db.command("usersInfo", "report_user")
for u in user_info.get("users", []):
for r in u.get("roles", []):
print(f" {r['role']}@{r['db']}")
# ── Drop a custom role when no longer needed ─────────────────────────────
# admin_db.command("dropRole", "obsoleteRole") # careful — affects all users with it
print("\nTo drop a role:")
print(" admin_db.command('dropRole', 'roleName')")
print(" ⚠ This immediately removes the role from ALL users who hold it")Granted analyticsReader to report_user
Revoked read@dataplexa from report_user
analyticsReader privileges after update:
orders aggregate, find
order_items aggregate, find
products aggregate, find
reviews aggregate, find
report_user current roles:
analyticsReader@admin
To drop a role:
admin_db.command('dropRole', 'roleName')
⚠ This immediately removes the role from ALL users who hold it
updateRolereplaces the entire privilege list — always include all existing privileges plus the new ones, not just the new ones, otherwise you accidentally remove existing accessgrantRolesToUserandrevokeRolesFromUsermodify the user's role list without affecting the role definitions themselves — use these for user-level access changes- Dropping a role takes effect immediately for all active connections that hold it — plan role drops carefully and communicate with application teams before executing
4. Field-Level Redaction with $redact
MongoDB's role system controls access at the collection level — but sometimes different users should see different fields within the same document. A customer service agent might see order details but not the payment method. An analytics user should see purchase amounts but not email addresses. The $redact aggregation stage implements field-level access control by pruning or keeping document branches based on a condition evaluated at query time.
# $redact — field-level access control in aggregation pipelines
from pymongo import MongoClient
client = MongoClient(
"mongodb://app_user:AppP@ss999!@localhost:27017/dataplexa",
authSource="dataplexa"
)
db = client["dataplexa"]
# ── Setup: products with sensitivity labels on fields ─────────────────────
# In a real system, sensitivity labels come from the document schema
# Here we simulate them with a 'clearance' field on sub-documents
db.products_secure.drop()
db.products_secure.insert_many([
{
"_id": "p001",
"name": "Wireless Mouse",
"category": "Electronics",
"price": 29.99,
"public_info": {
"clearance": "public",
"rating": 4.5,
"stock": 42,
},
"internal_info": {
"clearance": "internal",
"cost_price": 12.50, # supplier cost — internal only
"supplier_id": "sup_007",
"reorder_level": 10,
},
"restricted_info": {
"clearance": "restricted",
"margin_pct": 58.4, # margin data — restricted
"contract_expiry": "2025-12-31",
}
}
])
# ── $redact with $$KEEP, $$PRUNE, $$DESCEND ───────────────────────────────
# $$KEEP — keep this sub-document and all its children
# $$PRUNE — remove this sub-document and all its children
# $$DESCEND — keep this level, evaluate children recursively
def query_with_clearance(user_clearance: str):
"""Return product fields the user's clearance level permits."""
clearance_rank = {"public": 1, "internal": 2, "restricted": 3}
user_rank = clearance_rank.get(user_clearance, 0)
# Allowed clearance levels for this user
allowed = [k for k, v in clearance_rank.items() if v <= user_rank]
pipeline = [
{"$match": {"_id": "p001"}},
{"$redact": {
"$cond": {
# If the sub-document has a clearance field, check it
"if": {"$in": [{"$ifNull": ["$clearance", "public"]}, allowed]},
"then": "$$DESCEND", # clearance allowed — descend into children
"else": "$$PRUNE" # clearance too high — remove entirely
}
}},
{"$project": {"_id": 0, "name": 1, "category": 1,
"price": 1, "public_info": 1,
"internal_info": 1, "restricted_info": 1}}
]
return list(db.products_secure.aggregate(pipeline))
print("Field-level access by clearance level:\n")
for level in ["public", "internal", "restricted"]:
results = query_with_clearance(level)
doc = results[0] if results else {}
visible = [k for k in ["public_info", "internal_info", "restricted_info"]
if k in doc]
print(f" Clearance: {level:12} Visible sections: {visible}")Clearance: public Visible sections: ['public_info']
Clearance: internal Visible sections: ['public_info', 'internal_info']
Clearance: restricted Visible sections: ['public_info', 'internal_info', 'restricted_info']
$$DESCENDtells$redactto keep the current level and continue evaluating nested sub-documents — this is how it recursively applies the condition at every level of the document tree$redactoperates on the document structure at query time — the full document is stored in MongoDB, and the filtering happens in the aggregation pipeline before results are returned to the client- For simpler field hiding without recursive logic, a
$projectexclusion stage is sufficient — use$redactonly when the field visibility decision is data-driven (based on a field value in the document itself)
5. Role Hierarchy and Inheritance
Custom roles can inherit from other roles — including both built-in roles and other custom roles. This lets you build a clean privilege hierarchy without duplicating privilege definitions. A senior analyst role can inherit from the junior analyst role and add extra privileges on top, ensuring the junior role's access is always a subset of the senior role's.
# Role inheritance — building a privilege hierarchy
from pymongo import MongoClient
client = MongoClient(
"mongodb://dataplexa_admin:StrongP@ssword123!@localhost:27017/",
authSource="admin"
)
admin_db = client["admin"]
# ── Base role: junior analyst — read orders and products ──────────────────
admin_db.command("createRole", "juniorAnalyst",
privileges=[
{
"resource": {"db": "dataplexa", "collection": "orders"},
"actions": ["find", "aggregate"]
},
{
"resource": {"db": "dataplexa", "collection": "products"},
"actions": ["find", "aggregate"]
},
],
roles=[]
)
# ── Extended role: senior analyst — inherits junior + adds users access ───
admin_db.command("createRole", "seniorAnalyst",
privileges=[
{
# Senior analysts can also query users (anonymised reports)
"resource": {"db": "dataplexa", "collection": "users"},
"actions": ["find", "aggregate"]
},
],
roles=[
{"role": "juniorAnalyst", "db": "admin"} # inherit all junior privileges
]
)
# ── Top role: lead analyst — inherits senior + can write reports ──────────
admin_db.command("createRole", "leadAnalyst",
privileges=[
{
"resource": {"db": "dataplexa", "collection": "reports"},
"actions": ["find", "insert", "update", "remove"]
},
],
roles=[
{"role": "seniorAnalyst", "db": "admin"} # inherit senior (which includes junior)
]
)
# Show the full resolved privilege set for leadAnalyst
print("leadAnalyst — full resolved privilege set (including inherited):\n")
role_info = admin_db.command("rolesInfo",
{"role": "leadAnalyst", "db": "admin"},
showPrivileges=True,
showBuiltinRoles=False
)
for role_doc in role_info.get("roles", []):
print(f" Direct privileges:")
for priv in role_doc.get("privileges", []):
coll = priv["resource"].get("collection", "*")
actions = sorted(priv["actions"])
print(f" {coll:12} {', '.join(actions)}")
print(f"\n Inherited roles:")
for inherited in role_doc.get("inheritedRoles", []):
print(f" {inherited['role']}@{inherited['db']}")
# Hierarchy summary
print("\nRole hierarchy summary:\n")
hierarchy = [
("juniorAnalyst", "orders(r), products(r)"),
("seniorAnalyst", "inherits junior + users(r)"),
("leadAnalyst", "inherits senior + reports(rw)"),
]
for role, summary in hierarchy:
print(f" {role:18} {summary}")Direct privileges:
reports find, insert, remove, update
Inherited roles:
seniorAnalyst@admin
juniorAnalyst@admin
Role hierarchy summary:
juniorAnalyst orders(r), products(r)
seniorAnalyst inherits junior + users(r)
leadAnalyst inherits senior + reports(rw)
- Role inheritance is transitive —
leadAnalystinherits fromseniorAnalystwhich inherits fromjuniorAnalyst, so a lead analyst automatically has all junior and senior privileges - When you update a parent role's privileges, all child roles that inherit from it gain the new privileges immediately — no need to update each child role separately
- Keep role hierarchies shallow — more than three levels of inheritance becomes difficult to reason about and audit. Prefer broader roles at each level over deep chains
Summary Table
| Concept | What It Does | Key Rule |
|---|---|---|
read / readWrite |
Database-level read or read-write access | Apply per database — never use AnyDatabase variants for app users |
dbAdmin |
Index and schema management — no data access | Use with readWrite for developer accounts |
| Custom role | Collection-level privilege with specific actions | updateRole replaces the full privilege list — include all existing privileges |
grantRolesToUser |
Adds roles to an existing user | Takes effect immediately for new connections |
$redact |
Field-level access control in aggregation | $$KEEP, $$PRUNE, $$DESCEND control sub-document visibility |
| Role inheritance | Child role receives all parent role privileges | Keep hierarchies shallow — max 3 levels |
dropRole |
Deletes a role and removes it from all users | Immediate effect — coordinate with application teams first |
Practice Questions
Practice 1. What is the difference between dbAdmin and dbOwner and when would you use each?
Practice 2. Why must you include all existing privileges when calling updateRole, not just the new ones you want to add?
Practice 3. What do the three $redact special variables $$KEEP, $$PRUNE, and $$DESCEND do?
Practice 4. Why is the userAdmin role considered effectively a superuser role even though it cannot read or write data?
Practice 5. How does role inheritance work when you update a parent role's privileges?
Quiz
Quiz 1. Which built-in role grants only index management and statistics — with no ability to read or write documents?
Quiz 2. When creating a custom role, what two fields must every privilege object contain?
Quiz 3. What $redact variable should you use to keep the current sub-document level and continue evaluating the condition on its nested children?
Quiz 4. What is the effect of calling dropRole on a role that is currently assigned to active users?
Quiz 5. A leadAnalyst role inherits from seniorAnalyst which inherits from juniorAnalyst. If you add a new privilege to juniorAnalyst, which roles gain that privilege?
Next up — Monitoring & Logging: Using serverStatus, currentOp, mongostat, mongotop, and structured log analysis to keep a production MongoDB deployment healthy.