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
AuthenticationFailedkode 18 dari output asli. - Diagnosis dua arah: siapa identitas koneksi ini, dan user mana saja yang ada.
- Peran
authSourcesebagai 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:
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:
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:
print("terautentikasi sebagai: " + JSON.stringify(db.runCommand({ connectionStatus: 1 }).authInfo.authenticatedUsers))
print("jumlah user di " + db.getName() + ": " + db.getUsers().users.length)Output yang Dihasilkan:
terautentikasi sebagai: []
jumlah user di latihan_trouble: 0connectionStatus 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:
// 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.
| Gejala | Penyebab umum | Perbaikan |
|---|---|---|
| Kode 18 setelah rotasi password | Kredensial lama masih tersimpan di environment variable atau secret manager | Perbarui di semua sumber — aplikasi, CI, skrip |
| Kode 18 hanya di satu environment | User dibuat di database yang berbeda dari authSource | Tambahkan authSource=admin (atau db yang sesuai) di URI |
| Kode 18 padahal user baru dibuat | User dibuat di db lain, atau belum menunggu replikasi ke node tujuan | Verifikasi dengan db.getUsers() di db yang benar |
Kode 18 dengan MONGODB-X509 | Koneksi tidak memakai TLS atau sertifikat klien tidak cocok | Aktifkan TLS; samakan sertifikat dengan user |
Operasi ditolak Unauthorized kode 13 | Role tidak memuat aksi itu | db.grantRolesToUser dengan role yang sesuai |
| Server baru dengan auth: tidak bisa masuk sama sekali | User pertama belum dibuat; localhost exception sudah tidak berlaku | Buat user pertama lewat koneksi localhost |
Rangkuman #
- Kode 18 (
AuthenticationFailed) sengaja tidak menjelaskan penyebabnya; diagnosis membandingkan URI dengan keadaan server. connectionStatusmenjawab “koneksi ini siapa”,db.getUsers()menjawab “user mana yang ada” — dua arah pemeriksaan sebelum menyentuh password.authSourceadalah penyebab tersering: user diadminsementara URI memeriksa database aplikasi.- Mekanisme yang dipaksa (
SCRAM-SHA-1,MONGODB-X509tanpa TLS) menghasilkan kode 18 yang sama. - Kode 18 adalah identitas, kode 13 adalah perizinan — keduanya diperbaiki dengan cara berbeda.