Authentication memverifikasi identitas, tetapi identitas itu sendiri harus diciptakan lebih dulu: sebuah user dengan nama, kredensial, dan daftar role. Kebiasaan yang benar adalah satu user per aplikasi atau per orang — bukan satu user super yang dipakai semua orang. Chapter ini membahas siklus hidup user di MongoDB: membuat, memeriksa, mengubah role dan password, hingga menghapus.

Peta materi Chapter ini:

  • db.createUser() dan anatomi field-nya: user, pwd, roles, mechanisms, passwordDigestor.
  • Membaca daftar user dengan db.getUsers() dan usersInfo.
  • Mengubah hak secara bertahap (grantRolesToUser, revokeRolesFromUser) versus total (updateUser).
  • Mengganti password dengan changeUserPassword, lalu membersihkan user dengan dropUser.

Server latihan kita menjalankan authorization mati, tetapi penyimpanan user dan seluruh perintah manajemennya tetap berfungsi nyata — semua blok di chapter ini dieksekusi sungguhan pada database latihan_uji.

Membuat User Baru #

Perintah pembuat user adalah db.createUser(), dijalankan pada database tempat user itu akan hidup (authentication database). Kita buat user untuk sebuah aplikasi pelaporan:

js
use latihan_uji
// hapus sisa percobaan sebelumnya bila ada
try { db.dropUser("siti_operator") } catch (e) { print("user belum ada — buat baru") }
db.createUser({
  user: "siti_operator",
  pwd: "sandiSiti123",
  roles: [
    { role: "readWrite", db: "latihan_uji" },
    { role: "read", db: "latihan_rapor" }
  ]
})
db.getUsers()

Output yang Dihasilkan:

text
switched to db latihan_uji

user belum ada — buat baru

{
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1792261745, i: 1 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1792261745, i: 1 })
}
{
  users: [
    {
      _id: 'latihan_uji.siti_operator',
      userId: UUID('99ac7376-534f-4473-aedc-cf91d1dbf773'),
      user: 'siti_operator',
      db: 'latihan_uji',
      roles: [
        { role: 'readWrite', db: 'latihan_uji' },
        { role: 'read', db: 'latihan_rapor' }
      ],
      mechanisms: [ 'SCRAM-SHA-1', 'SCRAM-SHA-256' ]
    }
  ],
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1792261745, i: 1 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1792261745, i: 1 })
}

Mari kita bedah field-field pentingnya:

  1. user dan pwd: nama user dan password-nya dalam bentuk teks biasa. Nama lengkap user hasilnya adalah siti_operator@latihan_uji.
  2. roles: daftar role yang diberikan — setiap entri memadukan nama role dan database tempat role berlaku. Role readWrite di latihan_uji memberi hak baca-tulis di sana, sedangkan read di latihan_rapor hanya memberi hak baca.
  3. mechanisms pada output: MongoDB menyimpan kredensial untuk SCRAM-SHA-1 dan SCRAM-SHA-256 sekaligus — persis seperti yang kita lihat lewat saslSupportedMechs di Chapter Authentication.

db.getUsers() adalah pembungkus dari perintah usersInfo — db.runCommand({ usersInfo: 1 }) menghasilkan data yang sama. Untuk satu user, gunakan db.getUser("siti_operator") bentuk tepatnya.

passwordDigestor: Server atau Client #

Secara default, passwordDigestor: "server" membuat mongosh mengirim password apa adanya dan server yang melakukan hashing. Opsi "client" membalik pembagian kerja: mongosh meng-hash password di sisi klien dan hanya mengirim hasilnya. Opsi "client" hanya kompatibel dengan SCRAM-SHA-1, karena SCRAM-SHA-256 menuntut digest di server:

js
db.createUser({
  user: "dewi_sha1",
  pwd: "sandiDewi123",
  roles: [],
  mechanisms: ["SCRAM-SHA-1"],
  passwordDigestor: "client"
})
db.getUser("dewi_sha1")

Output yang Dihasilkan:

text
{
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1792261745, i: 2 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1792261745, i: 2 })
}
{
  _id: 'latihan_uji.dewi_sha1',
  userId: UUID('ed2cf8c6-f44e-498a-8496-dc36d4670d97'),
  user: 'dewi_sha1',
  db: 'latihan_uji',
  roles: [],
  mechanisms: [ 'SCRAM-SHA-1' ]
}

User dewi_sha1 tercatat hanya dengan mekanisme SCRAM-SHA-1 — itulah akibat membatasi field mechanisms. Mode "client" sesungguhnya warisan dari server-server lawas; untuk deployment modern, biarkan default "server" dan lindungi pengiriman password dengan TLS sebagaimana dibahas di Chapter TLS dan Enkripsi.

Mengubah Role: Bertahap vs Total #

Dua keluarga perintah mengubah role sebuah user, dengan semantik yang berbeda penting. grantRolesToUser dan revokeRolesFromUser bekerja bertahap — menambah atau melepas satu role tanpa menyentuh yang lain:

js
db.grantRolesToUser("siti_operator", [{ role: "dbAdmin", db: "latihan_uji" }])
db.revokeRolesFromUser("siti_operator", [{ role: "read", db: "latihan_rapor" }])
db.getUser("siti_operator")

Output yang Dihasilkan (bagian $clusterTime dan operationTime yang berulang diringkas):

text
{ ok: 1 }
{ ok: 1 }
{
  _id: 'latihan_uji.siti_operator',
  userId: UUID('99ac7376-534f-4473-aedc-cf91d1dbf773'),
  user: 'siti_operator',
  db: 'latihan_uji',
  roles: [
    { role: 'readWrite', db: 'latihan_uji' },
    { role: 'dbAdmin', db: 'latihan_uji' }
  ],
  mechanisms: [ 'SCRAM-SHA-1', 'SCRAM-SHA-256' ]
}

Setelah dua perintah itu, siti_operator memiliki readWrite dan dbAdmin di latihan_uji; role read di latihan_rapor sudah dilepas. Sebaliknya, db.updateUser() bekerja total — field roles yang diberikan menggantikan seluruh role lama:

js
db.updateUser("siti_operator", {
  roles: [{ role: "readWrite", db: "latihan_uji" }]
})
db.getUser("siti_operator")

Output yang Dihasilkan (diringkas serupa):

text
{ ok: 1 }
{
  _id: 'latihan_uji.siti_operator',
  userId: UUID('99ac7376-534f-4473-aedc-cf91d1dbf773'),
  user: 'siti_operator',
  db: 'latihan_uji',
  roles: [ { role: 'readWrite', db: 'latihan_uji' } ],
  mechanisms: [ 'SCRAM-SHA-1', 'SCRAM-SHA-256' ]
}

Daftar role kini berisi tepat satu entri readWrite — dbAdmin hilang tanpa perlu revoke manual. Pilih alat sesuai maksud: operasi bertahap untuk penyesuaian kecil, operasi total untuk menyatakan “hak user ini harus persis seperti ini”.

Mengganti Password #

db.changeUserPassword() mengganti kredensial sebuah user dalam satu perintah. Password lama langsung tidak berlaku — kita buktikan dengan mencoba keduanya:

js
db.changeUserPassword("siti_operator", "sandiBaru789")
db.auth("siti_operator", "sandiSiti123")

Output yang Dihasilkan (diringkas serupa):

text
{ ok: 1 }
Uncaught 
MongoServerError[AuthenticationFailed]: Authentication failed.

Password lama ditolak dengan AuthenticationFailed. Lanjut dengan password baru:

js
db.auth("siti_operator", "sandiBaru789")
db.logout()

Output yang Dihasilkan (diringkas serupa):

text
{ ok: 1 }
{ ok: 1 }

Baris { ok: 1 } pertama berasal dari db.auth() — password baru diterima — dan baris kedua dari db.logout() yang melepas identitas dari koneksi. Dalam operasional nyata, ganti password saat ada indikasi kredensial bocor dan saat rotasi berkala — topik yang kita dalami di Chapter Credential Management.

Menghapus User #

Penghapusan memakai db.dropUser(). Sebuah aplikasi yang pensiun harus meninggalkan user yang sudah tidak terpakai — user menganggur adalah pintu masuk yang terlupakan. Kita bersihkan kedua user percobaan:

js
db.dropUser("siti_operator")
db.dropUser("dewi_sha1")
db.getUsers()

Output yang Dihasilkan (diringkas serupa):

text
{ ok: 1 }
{ ok: 1 }
{
  users: [],
  ok: 1
}

Daftar user di latihan_uji kembali kosong. Untuk membersihkan sekaligus semua user pada satu database, jalankan perintah db.runCommand({ dropAllUsersFromDatabase: 1 }) — dan ada satu jebakan yang penting diketahui:

Rangkuman #

  • db.createUser() memadukan identitas (user), kredensial (pwd), dan otorisasi awal (roles) dalam satu perintah pada authentication database.
  • passwordDigestor: "server" (default) mengirim password untuk di-hash server; "client" hanya untuk SCRAM-SHA-1 dan praktis hanya relevan bagi server lawas.
  • grantRolesToUser/revokeRolesFromUser mengubah role secara bertahap; updateUser mengganti seluruh daftar role sekaligus.
  • changeUserPassword langsung mematikan password lama; verifikasi dengan db.auth() sebelum dan sesudah.
  • dropUser menghapus satu user; dropDatabase justru tidak menghapus user di database itu.