Koneksi TCP sudah sehat — ping berhasil, port terbuka — tetapi server tetap menolak dengan Authentication failed. Itulah tanda masalahnya sudah pindah lapisan: dari jaringan ke identitas. Galat autentikasi (kode 18, AuthenticationFailed) hampir selalu bermula dari empat hal: user tidak ada, password salah, authSource tidak cocok, atau mekanisme autentikasi yang tidak sesuai. Server latihan kita sengaja berjalan tanpa authentication, sehingga kita bisa mengamati perilaku galat ini dengan aman — tanpa risiko mengunci sesi kita sendiri di luar server.

Peta materi Chapter ini:

  • Membedah pesan AuthenticationFailed kode 18 dari output asli.
  • Diagnosis dua arah: siapa identitas koneksi ini, dan user mana saja yang ada.
  • Peran authSource sebagai penyebab paling sering di production.
  • Perbedaan galat 18 (authentication) dengan galat 13 (authorization).

Membedah Kode 18: AuthenticationFailed #

Kita panggil db.auth() dengan kredensial yang pasti salah, lalu tangkap objek galatnya untuk dibaca field demi field:

js
use latihan_trouble
try {
  db.auth("budi", "salahpass")
} catch (err) {
  print("name: " + err.name)
  print("code: " + err.code + " (" + err.codeName + ")")
  print("message: " + err.message)
}

Output yang Dihasilkan:

text
switched to db latihan_trouble
name: MongoServerError
code: 18 (AuthenticationFailed)
message: Authentication failed.

Tiga field itu adalah standar cara membaca galat MongoDB: name menunjukkan galat datang dari server (bukan dari driver atau jaringan), code 18 adalah nomor resmi AuthenticationFailed, dan message-nya sengaja tidak menjelaskan apa pun. Penggelapan ini disengaja — pesan seperti “password salah” atau “user tidak ditemukan” akan memberi tahu penyerang langkah berikutnya, sehingga server hanya menjawab “autentikasi gagal” untuk semua kombinasi yang salah. Konsekuensinya, beban diagnosis ada di sisi kita: membandingkan kredensial di URI dengan keadaan sebenarnya di server.

Diagnosis Dua Arah: Identitas dan Inventaris #

Bila galat 18 muncul di aplikasi, dua pertanyaan harus dijawab sebelum menyentuh password: koneksi ini membawa identitas siapa, dan user apa saja yang sebenarnya ada? Dua perintah berikut menjawab keduanya:

js
print("terautentikasi sebagai: " + JSON.stringify(db.runCommand({ connectionStatus: 1 }).authInfo.authenticatedUsers))
print("jumlah user di " + db.getName() + ": " + db.getUsers().users.length)

Output yang Dihasilkan:

text
terautentikasi sebagai: []

jumlah user di latihan_trouble: 0

connectionStatus menunjukkan identitas yang melekat pada koneksi saat ini: array authenticatedUsers yang kosong berarti sesi ini bekerja tanpa identitas — normal untuk server latihan tanpa auth, tetapi alarm di production. db.getUsers() (pembungkus usersInfo) mendaftar user yang hidup di database aktif; nol di sini menegaskan tidak ada user budi untuk dicek, sehingga kode 18 tadi sudah pasti bukan soal password.

Di production, pola bacanya sama: bandingkan authenticatedUsers dengan user yang menurut aplikasi sedang dipakai, lalu daftar user dengan db.getUsers() di database tempat user itu seharusnya dibuat. Bila user aplikasi ternyata dibuat di admin sementara URI mengarah ke database lain, kita sudah menemukan penyebabnya — kasus authSource.

authSource dan Mekanisme: Dua Jebakan URI #

User di MongoDB hidup di database tertentu, dan authSource adalah parameter URI yang menentukan database mana yang dipakai saat memeriksa kredensial. Tanpa authSource, driver memakai database dari path URI: mongodb://aplikasi:YOUR_PASSWORD@host/toko_buku akan mencari user aplikasi di database toko_buku. Bila user itu dibuat di admin — tempat yang wajar untuk user lintas database — hasilnya kode 18 meski password sudah benar. Perbaikannya satu kata: ?authSource=admin.

Jebakan kedua adalah mekanisme. mongosh dan driver modern memakai SCRAM-SHA-256 secara bawaan, dan server menegosiasikan mekanisme dari data user; kesalahan biasanya muncul saat aplikasi lama dipaksa memakai authMechanism=SCRAM-SHA-1 untuk user yang hanya punya kredensial SHA-256, atau sebaliknya. Autentikasi MONGODB-X509 punya syarat khusus: koneksi harus TLS dengan sertifikat klien. Semua condong ke gejala akhir yang sama — kode 18 — sehingga parameter URI adalah tempat kedua yang wajib diperiksa setelah kredensial.

Pembuatan user sendiri tidak kita jalankan di server bersama ini; bentuk perintahnya untuk server Anda sendiri:

js
// mdtest: skip — membuat user di server latihan bersama dilarang
// use toko_buku
// db.createUser({
//   user: "aplikasi",
//   pwd: passwordPrompt(),   // diminta interaktif, tidak tercatat di history shell
//   roles: [ { role: "readWrite", db: "toko_buku" } ]
// })

passwordPrompt() lebih aman daripada menulis password mentah: nilai tersebut tidak pernah tercatat di history shell maupun berkas log. Role diberikan per database — prinsip least privilege dibahas penuh di Section Keamanan.

Galat 18 Bukan Galat 13 #

Satu pembedaan penutup yang sering membingungkan: authentication (siapa Anda) berbeda dari authorization (apa yang boleh Anda lakukan). Bila koneksi berhasil tetapi operasi ditolak dengan Unauthorized kode 13, kredensial sudah benar — yang kurang adalah role untuk operasi tersebut. Solusinya adalah memberi role yang sesuai (db.grantRolesToUser), bukan mengutak-atik password atau authSource.

GejalaPenyebab umumPerbaikan
Kode 18 setelah rotasi passwordKredensial lama masih tersimpan di environment variable atau secret managerPerbarui di semua sumber — aplikasi, CI, skrip
Kode 18 hanya di satu environmentUser dibuat di database yang berbeda dari authSourceTambahkan authSource=admin (atau db yang sesuai) di URI
Kode 18 padahal user baru dibuatUser dibuat di db lain, atau belum menunggu replikasi ke node tujuanVerifikasi dengan db.getUsers() di db yang benar
Kode 18 dengan MONGODB-X509Koneksi tidak memakai TLS atau sertifikat klien tidak cocokAktifkan TLS; samakan sertifikat dengan user
Operasi ditolak Unauthorized kode 13Role tidak memuat aksi itudb.grantRolesToUser dengan role yang sesuai
Server baru dengan auth: tidak bisa masuk sama sekaliUser pertama belum dibuat; localhost exception sudah tidak berlakuBuat user pertama lewat koneksi localhost

Rangkuman #

  • Kode 18 (AuthenticationFailed) sengaja tidak menjelaskan penyebabnya; diagnosis membandingkan URI dengan keadaan server.
  • connectionStatus menjawab “koneksi ini siapa”, db.getUsers() menjawab “user mana yang ada” — dua arah pemeriksaan sebelum menyentuh password.
  • authSource adalah penyebab tersering: user di admin sementara URI memeriksa database aplikasi.
  • Mekanisme yang dipaksa (SCRAM-SHA-1, MONGODB-X509 tanpa TLS) menghasilkan kode 18 yang sama.
  • Kode 18 adalah identitas, kode 13 adalah perizinan — keduanya diperbaiki dengan cara berbeda.