Security: Users, Roles & Network Access

MongoDB · Lesson 23 of 28 9 min read Updated

A fresh local MongoDB install accepts every command from anyone who can reach it, with no password. That is convenient while you learn on your own laptop, and dangerous everywhere else. Databases that were exposed to the internet without authentication have been wiped and held for ransom many times. The shop's database holds names, emails and order histories, which are personal data under India's Digital Personal Data Protection Act, 2023, so protecting it is a legal duty as well as good practice.

In this lesson you will switch on authentication, create users with only the rights they need, control who can reach the server over the network, and defend your application code against NoSQL injection. Practise on your own local server or Atlas cluster.

Authentication and authorisation

Two words matter here:

  • Authentication answers "who are you?" A client proves its identity with a username and password (or a certificate). MongoDB's default password mechanism is SCRAM-SHA-256, which never sends the password itself over the network.
  • Authorisation answers "what may you do?" Each user has roles, and each role is a set of allowed actions on databases or collections. This is called role-based access control.

On Atlas both are always on. On a self-managed server you turn them on yourself.

Step 1: create an administrator

While a server has no users, MongoDB allows a connection from the same machine to create the first one. This is called the localhost exception. Connect with mongosh on the server and create an administrator in the admin database. passwordPrompt() asks for the password so it is not saved in your shell history:

MongoDB Shell
use admin
db.createUser({
  user: 'siteAdmin',
  pwd: passwordPrompt(),
  roles: [
    { role: 'userAdminAnyDatabase', db: 'admin' },
    { role: 'readWriteAnyDatabase', db: 'admin' }
  ]
})
Output
switched to db admin
{ ok: 1 }

mongosh asks for the password before it sends the command, then prints { ok: 1 }. userAdminAnyDatabase lets this account manage users everywhere, and readWriteAnyDatabase lets it work with data. Choose a long, unique password and keep it in a password manager.

Step 2: switch on access control

Edit the configuration file (mongod.cfg on Windows, /etc/mongod.conf on Linux) so that it contains these settings, then restart the MongoDB service:

YAML
net:
  port: 27017
  bindIp: 127.0.0.1
security:
  authorization: enabled

From now on, a client that has not logged in can connect but cannot read or write anything:

MongoDB Shell
db.getSiblingDB('shop').orders.findOne()
Output
MongoServerError[Unauthorized]: Command find requires authentication

Log in by passing the username when you start the shell, together with the database where the user was created (its authentication database): mongosh --username siteAdmin --authenticationDatabase admin.

Step 3: least privilege for applications

Never let your web application log in as an administrator. If the application is ever compromised, the attacker gets exactly the rights of its database user. Give each application its own user with the smallest set of roles it needs, which is called the principle of least privilege. The shop's backend only reads and writes the shop database:

MongoDB Shell
use shop
db.createUser({ user: 'shopApp', pwd: passwordPrompt(), roles: [{ role: 'readWrite', db: 'shop' }] })
db.createUser({ user: 'reportsBot', pwd: passwordPrompt(), roles: [{ role: 'read', db: 'shop' }] })

The application's connection string then names the user and its authentication database, for example mongodb://shopApp:<password>@127.0.0.1:27017/shop?authSource=shop, kept in an environment variable as shown in The Node.js Driver. The reporting job can read everything but change nothing.

The most useful built-in roles are:

RoleAllows
readReading all collections of one database
readWriteReading and writing data in one database, and creating indexes
dbAdminAdministrative tasks such as statistics and indexes, but not reading data
userAdminCreating and changing users and roles in one database
dbOwnerAll of the above for one database
readAnyDatabase, readWriteAnyDatabaseThe same as read / readWrite, for every database
backup, restoreWhat the backup tools need
rootEverything; keep for emergencies only

When no built-in role fits, create your own with db.createRole(). This role may only read the orders collection, which suits a delivery partner's tracking service:

MongoDB Shell
use shop
db.createRole({
  role: 'orderReader',
  privileges: [{ resource: { db: 'shop', collection: 'orders' }, actions: ['find'] }],
  roles: []
})
db.createUser({ user: 'tracking', pwd: passwordPrompt(), roles: ['orderReader'] })

Manage users with db.getUsers(), db.grantRolesToUser(), db.revokeRolesFromUser(), db.changeUserPassword() and db.dropUser(). Review the list regularly and remove accounts nobody uses.

Network access

Authentication is the lock; network rules decide who can even reach the door.

  • Bind only the interfaces you need. By default mongod listens on 127.0.0.1 only, so nothing outside the machine can connect. If an application server must connect, add that one private address to bindIp (for example 127.0.0.1,10.0.0.5), never a public address unless you have to, and never without authentication.
  • Use a firewall. Allow port 27017 only from your application servers' addresses.
  • Encrypt connections with TLS. Set net.tls.mode: requireTLS with a certificate so passwords and data cannot be read on the network. Atlas always requires TLS.

On Atlas, the same ideas appear in the user interface:

  • Database users are created under Database Access and are separate from your Atlas login. Give them built-in or custom roles exactly as above.
  • The IP access list under Network Access lists the addresses allowed to connect. Add your office or server address; 0.0.0.0/0 (anywhere) is only acceptable for a short test with strong passwords.
  • Private endpoints and network peering connect your cloud servers to Atlas without using the public internet, which is the safest option for production.

Data is also encrypted at rest on Atlas. For the most sensitive fields, such as Aadhaar or card numbers, MongoDB offers client-side field level encryption and Queryable Encryption, which encrypt a field in your application before it reaches the server. MongoDB 9.0 made prefix, suffix and substring searches on such encrypted text fields generally available.

NoSQL injection in application code

Even with perfect users and firewalls, careless code can leak data. A login handler that puts request data straight into a query is vulnerable:

JavaScript
// UNSAFE: req.body comes from the user
const user = await db.collection('customers').findOne({
  email: req.body.email,
  password: req.body.password
});

If an attacker sends the JSON { "email": "aarav@example.com", "password": { "$ne": "" } }, the password condition becomes "not equal to an empty string", which matches, and they log in without a password. The fix is to make sure values are the type you expect before they reach a query, and to never compare passwords in a query at all:

JavaScript
const email = String(req.body.email ?? '').trim().toLowerCase();
const user = await db.collection('customers').findOne({ email });
// then check the password with a slow hash such as bcrypt or argon2,
// comparing against the stored hash in your code

Validating input with a schema library, or turning on Mongoose's sanitizeFilter option, which strips $ operators from filters built from user input, gives a second layer of protection. Store only password hashes, never passwords.

Common mistakes

  • Running without authorization: enabled. Any program that can reach the port has full control.
  • Binding to 0.0.0.0 "to make it work". That exposes the server to every network the machine is on. Bind specific addresses and use a firewall.
  • One admin account for everything. Give each application and person its own user with minimal roles.
  • Passwords in code or Git. Keep connection strings in environment variables or a secrets manager.
  • Building filters directly from request data. Check types and strip $ keys before querying.

Key points

  • Authentication proves who a client is (SCRAM-SHA-256 by default); authorisation uses roles to limit what it may do.
  • Create the first administrator through the localhost exception, then set security.authorization: enabled.
  • Give every application its own user with the smallest roles it needs, such as readWrite on one database.
  • Custom roles grant specific actions on specific collections.
  • Bind mongod to the addresses you need, use a firewall and TLS; on Atlas, use database users, the IP access list and private endpoints.
  • Prevent NoSQL injection by validating input types and never passing raw request objects as filters.

Exercise

On your local server with access control enabled, create a user analyst who can read the shop database but write nothing, and a custom role stockKeeper that can only read and update the products collection. Assign stockKeeper to a new user warehouse. Then log in as analyst and confirm that an updateOne() on products is refused.

Show solution
MongoDB Shell
use shop
db.createUser({ user: 'analyst', pwd: passwordPrompt(), roles: [{ role: 'read', db: 'shop' }] })
db.createRole({
  role: 'stockKeeper',
  privileges: [{ resource: { db: 'shop', collection: 'products' }, actions: ['find', 'update'] }],
  roles: []
})
db.createUser({ user: 'warehouse', pwd: passwordPrompt(), roles: ['stockKeeper'] })

Log in with mongosh --username analyst --authenticationDatabase shop, run use shop and try db.products.updateOne({ _id: 101 }, { $inc: { stock: 1 } }). The server refuses with an Unauthorized error saying the user is not authorised to run the update, while find() works. warehouse can read and update products but cannot see orders or customers.

Quick Quiz

1. What is the localhost exception?

2. Which role should a typical web application's database user have?

3. What does net.bindIp: 127.0.0.1 do?

4. An attacker sends { "password": { "$ne": "" } } to a login form. What prevents the attack?

Learn MongoDB with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call