This post is also available in:
When you need to copy a user from one MongoDB instance to another and keep the exact same credentials, db.createUser() won’t work. It always generates a new password hash. The only way to preserve credentials is through a direct insert into the system.users collection. This approach works on any topology: single instance, replica set, or shard.
This post documents the full process, tested in a production environment.
Why db.createUser() doesn’t work here
db.createUser() always derives new SCRAM-SHA-1 and SCRAM-SHA-256 hashes from a plaintext password. If you don’t have the original password, there’s no way to recreate the same credentials through the standard method.
The alternative is to extract the full document from system.users on the source and insert it directly on the destination.
Step 0: Extract the user on the source
Connect to the source instance and run:
db.getSiblingDB("admin").system.users.find(
{ user: "db_app_view", db: "admin" }
).toArray()Expected output:
[
{
"_id": "admin.db_app_view",
"user": "db_app_view",
"db": "admin",
"credentials": {
"SCRAM-SHA-1": {
"iterationCount": 10000,
"salt": "<SALT_SHA1>",
"storedKey": "<STORED_KEY_SHA1>",
"serverKey": "<SERVER_KEY_SHA1>"
},
"SCRAM-SHA-256": {
"iterationCount": 15000,
"salt": "<SALT_SHA256>",
"storedKey": "<STORED_KEY_SHA256>",
"serverKey": "<SERVER_KEY_SHA256>"
}
},
"roles": [
{ "role": "IPsPermitidos_users", "db": "admin" },
{ "role": "readAnyDatabase", "db": "admin" }
]
}
]Save all fields under credentials. These are the hashes you’ll replicate.
The problem: direct inserts are blocked by default
On the destination, a direct insert into system.users returns:
MongoServerError[Unauthorized]: not authorized on admin to execute command
{ insert: "system.users", ... }Even when connected as admin, MongoDB blocks direct writes to system collections. The admin user holds userAdminAnyDatabase, but that role does not grant bypass access to internal collections.
Solution: temporarily grant __system
⚠️ Warning: The
__systemrole grants unrestricted access to the server. Grant it only for the duration of this operation and revoke it immediately after.
Step 1: Grant __system to the admin user
use admin
db.grantRolesToUser("admin", [{ role: "__system", db: "admin" }])Expected output:
{ "ok": 1 }Step 2: Insert the cloned user
Replace the credentials values with those extracted from the source. Adjust roles as needed for the destination environment.
db.getSiblingDB("admin").system.users.insertOne({
_id: "admin.db_app_view",
user: "db_app_view",
db: "admin",
credentials: {
"SCRAM-SHA-1": {
iterationCount: 10000,
salt: "<SALT_SHA1>",
storedKey: "<STORED_KEY_SHA1>",
serverKey: "<SERVER_KEY_SHA1>"
},
"SCRAM-SHA-256": {
iterationCount: 15000,
salt: "<SALT_SHA256>",
storedKey: "<STORED_KEY_SHA256>",
serverKey: "<SERVER_KEY_SHA256>"
}
},
roles: [
{ role: "IPsPermitidos_users", db: "admin" },
{ role: "read", db: "pix" }
]
})Expected output:
{ "acknowledged": true, "insertedId": "admin.db_app_view" }💡 Note: Roles on the destination don’t have to match the source. Adjust them to fit the target environment.
Step 3: Invalidate the auth cache
db.adminCommand({ invalidateUserCache: 1 })MongoDB keeps an in-memory cache of users and roles to avoid reading system.users on every authentication request. A direct insert bypasses the normal update path, so the cache is not refreshed automatically. Running invalidateUserCache forces the server to reload authentication data from disk immediately, making the new user available without a restart.
Expected output:
{ "ok": 1 }Step 4: Revoke __system
⚠️ This step is mandatory. Do not leave the
adminuser with the__systemrole in production.
db.revokeRolesFromUser("admin", [{ role: "__system", db: "admin" }])Expected output:
{ "ok": 1 }Verify the result
Confirm the user was created correctly:
db.getSiblingDB("admin").system.users.find(
{ user: "db_app_view", db: "admin" }
).toArray()Test authentication with the original credentials:
db.auth("db_app_view", "<original_password>")Quick checklist
[ ] Extract system.users document on the source
[ ] Connect to the destination primary or instance
[ ] Grant __system to admin
[ ] Insert the document with insertOne into system.users
[ ] Run invalidateUserCache
[ ] Revoke __system from admin
[ ] Verify user with find on system.users
[ ] Test authenticationWhen to use this approach
This method is necessary when:
- The original password is not available (only the hash)
- You are migrating users between environments without a password reset
- You need exact credential replication (e.g., infrastructure rotation without application impact)
- You are moving users between different topologies (e.g., single instance to replica set)
If the original password is available, prefer db.createUser() on the destination. Direct inserts into system.users should be treated as a surgical operation, not a routine procedure.
