R
Rishtaara
MongoDB Fundamentals
Lesson 36 of 40Article16 min

MongoDB Security

MongoDB security is layered — network isolation, encryption, authentication, authorization, and auditing. Skipping any layer exposes your data. Never run a production database with no authentication on a public IP.

Defense in depth for MongoDB

MongoDB security is layered — network isolation, encryption, authentication, authorization, and auditing. Skipping any layer exposes your data. Never run a production database with no authentication on a public IP.

Real-life example: Securing MongoDB is like securing a bank — fence (firewall), locked doors (auth), ID badges (RBAC), cameras (audit logs), and armored trucks (TLS encryption).

MongoDB security layers

Enable access control and authentication

Start mongod with --auth or enable authorization in the config file. Create an admin user first, then application-specific users with least privilege.

SCRAM-SHA-256 is the default authentication mechanism. x.509 certificates work for internal cluster member auth.

Real-life example: Creating separate database users is giving each employee a key card that opens only their floor — the intern cannot enter the vault.

Create admin and application users
// Connect without auth on first setup, create admin
use admin
db.createUser({
  user: "adminUser",
  pwd: "Very-Strong-Random-Password-123!",
  roles: [{ role: "userAdminAnyDatabase", db: "admin" }]
})

// Application user — readWrite only on rishtaara database
use rishtaara
db.createUser({
  user: "rishtaara_app",
  pwd: process.env.MONGO_APP_PASSWORD,
  roles: [{ role: "readWrite", db: "rishtaara" }]
})

// Read-only user for reporting dashboards
db.createUser({
  user: "rishtaara_readonly",
  pwd: process.env.MONGO_READONLY_PASSWORD,
  roles: [{ role: "read", db: "rishtaara" }]
})

Role-based access control (RBAC)

  • Built-in roles: read, readWrite, dbAdmin, userAdmin, clusterAdmin, root.
  • Custom roles combine specific privileges: { resource: { db: "rishtaara", collection: "orders" }, actions: ["find", "insert"] }.
  • Principle of least privilege — your API server gets readWrite on its database, not root.
  • Atlas IAM integration and LDAP/SAML for enterprise single sign-on.
Custom role — insert-only on logs collection
use rishtaara
db.createRole({
  role: "logWriter",
  privileges: [
    {
      resource: { db: "rishtaara", collection: "logs" },
      actions: ["insert"]
    }
  ],
  roles: []
})

db.createUser({
  user: "log_service",
  pwd: "service-password",
  roles: [{ role: "logWriter", db: "rishtaara" }]
})

TLS, connection strings, and environment variables

Always use TLS in production. Atlas enforces TLS by default. Self-hosted clusters need certificates configured on mongod and in the connection string (?tls=true).

Store credentials in environment variables — never commit connection strings to Git. Use .env locally and secret managers (AWS Secrets Manager, Vault) in production.

Real-life example: Putting passwords in source code is writing your house key under the doormat — anyone who clones the repo gets in.

Secure connection string patterns
# .env — never commit this file
MONGODB_URI=mongodb+srv://rishtaara_app:<PASSWORD>@cluster0.xxxxx.mongodb.net/rishtaara?retryWrites=true&w=majority

# Node.js
await mongoose.connect(process.env.MONGODB_URI);

NoSQL injection — validate all input

NoSQL injection happens when user input becomes part of a query object without sanitization. An attacker sends { "email": { "$gt": "" } } instead of a string and bypasses login checks.

Real-life example: NoSQL injection is tricking the receptionist with a fake ID format — 'anyone whose name is greater than empty string' matches every record.

Tip: Use mongoose schema types, celebrate/joi validation, and never pass req.body directly into find(), findOne(), or updateOne() filters.
Vulnerable vs safe login query
// VULNERABLE — user body merged into query
app.post("/login", async (req, res) => {
  const user = await User.findOne(req.body); // attacker sends { email: { $gt: "" } }
});

// SAFE — explicit typed fields only
app.post("/login", async (req, res) => {
  const email = String(req.body.email ?? "").trim().toLowerCase();
  const password = String(req.body.password ?? "");
  if (!email || !password) return res.status(400).json({ error: "Missing fields" });

  const user = await User.findOne({ email }).select("+passwordHash");
  // verify password with bcrypt...
});