GRANT dan role dari chapter-chapter sebelumnya mengatur apa yang boleh dilakukan seseorang — tetapi semua aturan itu percuma bila server salah mengenali siapa yang menelepon. Authentication adalah pembuktian identitas: proses satu-satunya tempat kata sandi diuji dan akun dipilih. Proses ini berlangsung dalam hitungan milidetik setiap kali klien menyambung, melibatkan sebuah plugin autentikasi yang memutuskan bagaimana pembuktian dilakukan, dan berakhir dengan server memilih tepat satu baris di mysql.user. Pada chapter ini kita bedah ketiga lapisan itu: plugin yang dipakai MySQL 8.4 (termasuk realita bahwa mysql_native_password kini sudah dimatikan), hash satu arah yang tersimpan, dan aturan pencocokan host yang bisa membuat dua akun bernama sama berperilaku berbeda.

Plugin Autentikasi: Para Penjaga Pintu #

Setiap akun di mysql.user mencantumkan satu plugin autentikasi di kolom plugin — program kecil yang menentukan protokol pembuktian identitasnya. Kita lihat plugin milik akun-akun yang ada:

sql
SELECT user, host, plugin FROM mysql.user ORDER BY user, host;

Output yang Dihasilkan:

text
+------------------+-----------+-----------------------+
| user             | host      | plugin                |
+------------------+-----------+-----------------------+
| mysql.infoschema | localhost | caching_sha2_password |
| mysql.session    | localhost | caching_sha2_password |
| mysql.sys        | localhost | caching_sha2_password |
| root             | localhost | caching_sha2_password |
+------------------+-----------+-----------------------+
4 rows in set (0.00 sec)

Seluruh akun bawaan memakai caching_sha2_password — dan itu memang plugin default MySQL sejak versi 8.0. Baru kita buat akun tanpa menentukan plugin, hasilnya tetap sama:

sql
DROP USER IF EXISTS 'aplikasi_toko'@'localhost';
CREATE USER 'aplikasi_toko'@'localhost' IDENTIFIED BY 'SandiKuat2026!';

SELECT user, host, plugin FROM mysql.user WHERE user = 'aplikasi_toko';

Output yang Dihasilkan:

text
Query OK, 0 rows affected, 1 warning (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

+---------------+-----------+-----------------------+
| user          | host      | plugin                |
+---------------+-----------+-----------------------+
| aplikasi_toko | localhost | caching_sha2_password |
+---------------+-----------+-----------------------+
1 row in set (0.00 sec)

Plugin mana yang jadi default diatur oleh variabel authentication_policy, dan daftar plugin autentikasi yang terpasang di server bisa kita baca dari information_schema:

sql
SELECT @@authentication_policy;

SELECT PLUGIN_NAME, PLUGIN_STATUS FROM information_schema.PLUGINS
WHERE PLUGIN_NAME LIKE '%password%' ORDER BY PLUGIN_NAME;

Output yang Dihasilkan:

text
+-------------------------+
| @@authentication_policy |
+-------------------------+
| *,,                     |
+-------------------------+
1 row in set (0.00 sec)

+-----------------------+---------------+
| PLUGIN_NAME           | PLUGIN_STATUS |
+-----------------------+---------------+
| caching_sha2_password | ACTIVE        |
| mysql_native_password | DISABLED      |
| sha256_password       | ACTIVE        |
+-----------------------+---------------+
3 rows in set (0.00 sec)

Nilai '*,,' pada kebijakan berarti “plugin mana pun diperbolehkan untuk akun, dua bagian kosong tanpa persyaratan tambahan” — kebijakan longgar yang menyerahkan pilihan pada default server. Tiga baris hasil query kedua memperlihatkan caching_sha2_password dalam status ACTIVE, sha256_password sebagai pelengkap, dan satu baris yang mencuri perhatian: mysql_native_password berstatus DISABLED.

Realita MySQL 8.4: mysql_native_password Sudah Dimatikan #

Plugin lawas mysql_native_password — protokol challenge-response berbasis hash SHA1 — mengantongi sejarah panjang dan banyak aplikasi tua yang masih bergantung padanya. Di MySQL 8.4 statusnya resmi turun: plugin masih ikut terpasang tetapi dimatikan secara default. Kita uji sendiri konsekuensinya:

sql
-- @error
CREATE USER 'u_native'@'localhost' IDENTIFIED WITH mysql_native_password BY 'SandiKuat2026!';

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

ERROR 1524 (HY000): Plugin 'mysql_native_password' is not loaded
Bye

Server menolak dengan ERROR 1524 (HY000): Plugin 'mysql_native_password' is not loaded — akun tidak dibuat. Bila server lama memang mengharuskannya, administrator dapat mengaktifkannya kembali dengan opsi startup --mysql-native-password=ON, tetapi arah perkembangan MySQL sudah jelas meninggalkannya. Kabar baiknya: aplikasi modern yang memakai konektor terbaru tidak menyadari perbedaan apa pun, karena protokol caching_sha2_password sudah ditangani Connector/NET, mysqlnd, Connector/J, dan kawan-kawannya secara bawaan.

Kata Sandi Tersimpan sebagai Hash Sekali Jalan #

Kolom authentication_string tidak menyimpan kata sandi — yang tersimpan adalah hash: hasil olahan satu arah yang tidak bisa dibalik kembali menjadi teks asli. Kita ukur wujudnya tanpa menampilkan hash penuh:

sql
SELECT LENGTH(authentication_string) AS panjang_hash,
       LEFT(authentication_string, 7) AS prefiks
FROM mysql.user WHERE user = 'aplikasi_toko';

Output yang Dihasilkan:

text
+--------------+---------+
| panjang_hash | prefiks |
+--------------+---------+
|           70 | $A$005$ |
+--------------+---------+
1 row in set (0.00 sec)

Panjang selalu 70 karakter, dan prefiks $A$005$ menandai keluarga caching_sha2_password dengan biaya iterasi tertentu. Bagian sisanya dihasilkan dari kata sandi ditambah garam acak: dua akun dengan kata sandi yang sama persis akan menghasilkan authentication_string yang berbeda, sehingga penyerang tidak bisa mencocokkan pola hash lintas akun. Saat login, server tidak pernah “membalik” hash — plugin mengirim tantangan, klien membuktikan diri dengan mengolah kata sandi, dan cocok-tidaknya dibandingkan di sisi server tanpa kata sandi pernah melintas kabel dalam bentuk asli.

Alur Autentikasi: Mencocokkan User dan Host #

Saat klien menyambung, server menjalankan urutan tetap. Pertama, mencari semua baris mysql.user yang nama usernya cocok. Kedua, menyaring berdasarkan host pengirim — baris dengan pola host paling spesifik yang cocok menang. Ketiga, plugin autentikasi pada baris terpilih menguji kata sandi. Baru setelah ketiganya lolos, koneksi diikat ke akun itu dan CURRENT_USER()-nya ditetapkan. Aturan “paling spesifik menang” ini bisa kita buktikan dengan dua akun bernama sama:

sql
DROP USER IF EXISTS 'staf'@'localhost';
DROP USER IF EXISTS 'staf'@'%';
CREATE USER 'staf'@'localhost' IDENTIFIED BY 'SandiLokal2026!';
CREATE USER 'staf'@'%' IDENTIFIED BY 'SandiRemote2026!';

SELECT user, host, plugin FROM mysql.user WHERE user = 'staf' ORDER BY host;

Output yang Dihasilkan:

text
Query OK, 0 rows affected, 1 warning (0.00 sec)

Query OK, 0 rows affected, 1 warning (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

+------+-----------+-----------------------+
| user | host      | plugin                |
+------+-----------+-----------------------+
| staf | %         | caching_sha2_password |
| staf | localhost | caching_sha2_password |
+------+-----------+-----------------------+
2 rows in set (0.00 sec)

Dua akun bernama staf kini hidup berdampingan: versi localhost untuk mesin yang sama, versi % untuk jaringan luas, masing-masing dengan kata sandi sendiri. Kita konek dari mesin lokal dengan kata sandi versi lokal:

bash
-- @test-bash
MYSQL_PWD='SandiLokal2026!' mysql -h 127.0.0.1 -P 3306 -u staf -t -e "SELECT USER() AS identitas_klien, CURRENT_USER() AS akun_cocok;"

Output yang Dihasilkan:

text
+-----------------+----------------+
| identitas_klien | akun_cocok     |
+-----------------+----------------+
| staf@localhost  | staf@localhost |
+-----------------+----------------+

Kedua fungsi sepakat: koneksi ini jatuh ke staf@localhost, bukan staf@% — pola localhost lebih spesifik daripada wildcard. Sekarang kita coba kata sandi milik versi wildcard:

bash
-- @test-bash
MYSQL_PWD='SandiRemote2026!' mysql -h 127.0.0.1 -P 3306 -u staf -t -e "SELECT 1;"

Output yang Dihasilkan:

text
ERROR 1045 (28000): Access denied for user 'staf'@'localhost' (using password: YES)

Login ditolak dengan ERROR 1045 — dan perhatikan bentuk akun yang disebut: 'staf'@'localhost'. Server tetap memilih baris localhost (paling spesifik) lalu menguji kata sandinya; sandi milik akun % tidak pernah tersentuh. Jadi versi wildcard bukan “cadangan” bila sandi lokal salah — akun yang dipilih selalu satu, sebelum uji sandi dimulai.

auth_socket dan Plugin Lainnya #

Di luar keluarga kata-sandi, MySQL mengenal plugin autentikasi berbasis identitas sistem operasi. auth_socket (khusus Linux) membenarkan koneksi Unix socket bila nama user sistem operasi klien sama dengan nama user MySQL — nyaman untuk administrasi tanpa sandi, tetapi hanya berlaku sesama pengguna satu mesin. Untuk kebutuhan perusahaan ada pula plugin kerberos, LDAP, dan FIDO/WebAuthn. Semuanya mengikuti pola yang sama dengan yang kita bedah: satu plugin tercatat per akun di mysql.user, dan klien harus mendukung protokolnya agar pembuktian berhasil.

Membersihkan Latihan #

Tiga akun latihan kita jatuhkan dari terminal:

bash
-- @test-bash
mysql -h 127.0.0.1 -P 3306 -u root -e "DROP USER IF EXISTS 'staf'@'localhost'; DROP USER IF EXISTS 'staf'@'%'; DROP USER IF EXISTS 'aplikasi_toko'@'localhost'; DROP USER IF EXISTS 'u_native'@'localhost';"

Referensi #

Pintu masuk kini terkunci dengan benar: identitas dibuktikan lewat plugin, hash tersimpan satu arah, dan akun dipilih dengan aturan host yang jelas. Pertanyaan berikutnya menyentuh kualitas kunci itu sendiri — kata sandi seperti apa yang layak diterima server? Chapter berikutnya memasang komponen validate_password, menguji penolakan kata sandi lemah dengan ERROR 1819, lalu menambatkan akun yang terlalu sering gagal login.