Mengelola hak akses lewat GRANT langsung bekerja baik untuk dua-tiga akun. Namun bayangkan tim data dengan sepuluh anggota yang semuanya butuh hak sama: sepuluh rangkaian GRANT yang harus diketik identik, dan setiap perubahan kebijakan berarti sepuluh pembaruan lagi — satu saja yang terlewat, dan anggota tim itu diam-diam punya hak berbeda. Role menyelesaikan pola ini: role adalah wadah berisi kumpulan privilege yang kita definisikan sekali, lalu bagikan ke akun mana pun. Hak tidak lagi menempel langsung ke orang, melainkan ke peran yang dijalankan orang itu. Pada chapter ini kita membangun role untuk tim pengembang dan pembaca laporan, mengaktifkannya dalam sesi nyata, lalu membongkar mitos terbesarnya: role yang diberikan belum tentu aktif.
Role: Akun yang Tidak Bisa Login #
Secara struktur internal, role memang satu baris di mysql.user — tetapi baris yang terkunci. Kita buat dua role sekaligus untuk skenario toko kita, lalu buktikan klaimnya:
DROP DATABASE IF EXISTS belajar_aman;
CREATE DATABASE belajar_aman;
USE belajar_aman;
CREATE TABLE produk (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(60) NOT NULL,
kategori VARCHAR(30) NOT NULL,
harga DECIMAL(10,2) NOT NULL,
stok INT NOT NULL
);
INSERT INTO produk (nama, kategori, harga, stok) VALUES
('Kopi Arabika Gayo', 'Minuman', 85000.00, 40),
('Teh Melati Premium', 'Minuman', 45000.00, 120),
('Gula Aren Cair', 'Bahan', 32000.00, 75),
('Susu Kental Manis', 'Bahan', 18000.00, 200),
('Sirup Jeruk', 'Minuman', 27000.00, 0);
DROP ROLE IF EXISTS 'app_dev', 'app_readonly';
DROP USER IF EXISTS 'budi_analyst'@'%';
CREATE ROLE 'app_dev', 'app_readonly';
SELECT user, host, account_locked FROM mysql.user
WHERE user IN ('app_dev', 'app_readonly') ORDER BY user;Output yang Dihasilkan:
Query OK, 0 rows affected, 1 warning (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Database changed
Query OK, 0 rows affected (0.00 sec)
Query OK, 5 rows affected (0.00 sec)
Records: 5 Duplicates: 0 Warnings: 0
Query OK, 0 rows affected, 2 warnings (0.00 sec)
Query OK, 0 rows affected, 1 warning (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
+--------------+------+----------------+
| user | host | account_locked |
+--------------+------+----------------+
| app_dev | % | Y |
| app_readonly | % | Y |
+--------------+------+----------------+
2 rows in set (0.00 sec)Kolom account_locked bernilai Y untuk keduanya — inilah mekanisme yang membuat role tidak bisa dipakai login, sekalipun ia punya baris di mysql.user seperti akun biasa. Nama role tanpa host secara otomatis mendapat host '%' dan diberi tanda kutip terbalik saat ditampilkan (app_dev@%). Selanjutnya kita isi kedua wadah itu dengan privilege:
GRANT SELECT, INSERT, UPDATE, DELETE ON belajar_aman.* TO 'app_dev';
GRANT SELECT ON belajar_aman.* TO 'app_readonly';Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 0 rows affected (0.00 sec)Sintaksnya identik dengan GRANT biasa — hanya penerimanya role. Role app_dev kini membawa hak CRUD penuh di belajar_aman, dan app_readonly hanya hak baca. Perbedaan privilege antar peran terlihat jelas di satu tempat, siap dibagikan tanpa diketik ulang.
Membagikan Role dan Melihat Isinya #
Pembagian role memakai bentuk khusus GRANT nama_role TO user. Kita buat akun budi_analyst danjadikan anggota tim pengembang:
CREATE USER 'budi_analyst'@'%' IDENTIFIED BY 'SandiKuat2026!';
GRANT 'app_dev' TO 'budi_analyst'@'%';
SHOW GRANTS FOR 'budi_analyst'@'%';
SHOW GRANTS FOR 'budi_analyst'@'%' USING 'app_dev';Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
+-------------------------------------------+
| Grants for budi_analyst@% |
+-------------------------------------------+
| GRANT USAGE ON *.* TO `budi_analyst`@`%` |
| GRANT `app_dev`@`%` TO `budi_analyst`@`%` |
+-------------------------------------------+
2 rows in set (0.00 sec)
+--------------------------------------------------------------------------------+
| Grants for budi_analyst@% |
+--------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `budi_analyst`@`%` |
| GRANT SELECT, INSERT, UPDATE, DELETE ON `belajar_aman`.* TO `budi_analyst`@`%` |
| GRANT `app_dev`@`%` TO `budi_analyst`@`%` |
+--------------------------------------------------------------------------------+
3 rows in set (0.00 sec)Dua bentuk SHOW GRANTS di atas memperlihatkan dua sudut pandang. Tanpa USING, kita melihat hak yang melekat langsung pada akun: hanya GRANT USAGE (tanda tanpa privilege global) plus baris GRANT app_dev@%TObudi_analyst@%`` — keanggotaan role, bukan isinya. Dengan USING 'app_dev', MySQL membuka wadah itu di depan mata kita: baris GRANT SELECT, INSERT, UPDATE, DELETE ON belajar_aman.* muncul seolah melekat langsung. Perbedaan dua tampilan ini penting saat audit: hak efektif user = hak langsung + isi semua role aktifnya.
Role Tidak Otomatis Aktif: SET ROLE #
Inilah mitos terbesar sekaligus jebakan klasik pemula: role yang sudah diberikan belum aktif dalam sesi. Haknya baru berlaku setelah role diaktifkan. Sebagai administrator di sesi root, kita tidak menyadarinya karena root tidak memakai role:
SELECT CURRENT_ROLE();Output yang Dihasilkan:
+----------------+
| CURRENT_ROLE() |
+----------------+
| NONE |
+----------------+
1 row in set (0.00 sec)NONE — sesi root kita berjalan tanpa role aktif. Sekarang kita masuk sebagai budi_analyst dari terminal dan melihat apa yang terjadi sebelum dan sesudah aktivasi:
-- @test-bash
MYSQL_PWD='SandiKuat2026!' mysql -h 127.0.0.1 -P 3306 -u budi_analyst -t -e "SELECT CURRENT_ROLE() AS peran_aktif; SELECT COUNT(*) FROM belajar_aman.produk;"Output yang Dihasilkan:
+-------------+
| peran_aktif |
+-------------+
| NONE |
+-------------+
ERROR 1142 (42000) at line 1: SELECT command denied to user 'budi_analyst'@'localhost' for table 'produk'CURRENT_ROLE() melaporkan NONE dan query berikutnya ditolak dengan ERROR 1142: SELECT command denied — padahal SHOW GRANTS USING 'app_dev' tadi jelas menunjukkan hak CRUD. Haknya ada, tetapi belum “dipakai”. Aktivasi dilakukan dengan SET ROLE di dalam sesi yang bersangkutan:
-- @test-bash
MYSQL_PWD='SandiKuat2026!' mysql -h 127.0.0.1 -P 3306 -u budi_analyst -t -e "SET ROLE 'app_dev'; SELECT CURRENT_ROLE() AS peran_aktif; SELECT COUNT(*) AS jml_produk FROM belajar_aman.produk;"Output yang Dihasilkan:
+---------------+
| peran_aktif |
+---------------+
| `app_dev`@`%` |
+---------------+
+------------+
| jml_produk |
+------------+
| 5 |
+------------+Setelah SET ROLE 'app_dev', fungsi CURRENT_ROLE() melaporkan app_dev@% dan penghitungan baris berhasil — 5 produk terbaca. Aktivasi berlaku hanya untuk sesi itu; koneksi baru kembali selangka lagi dengan NONE. Untuk tim yang tidak mau repot, SET DEFAULT ROLE membuat role aktif otomatis setiap kali anggotanya login:
-- @test-bash
mysql -h 127.0.0.1 -P 3306 -u root -e "SET DEFAULT ROLE 'app_dev' TO 'budi_analyst'@'%';"
MYSQL_PWD='SandiKuat2026!' mysql -h 127.0.0.1 -P 3306 -u budi_analyst -t -e "SELECT CURRENT_ROLE() AS peran_default;"Output yang Dihasilkan:
+---------------+
| peran_default |
+---------------+
| `app_dev`@`%` |
+---------------+Statement pertama (dijalankan sebagai root) menetapkan role default untuk akun budi_analyst; koneksi kedua lalu menemukan app_dev@% sudah aktif tanpa SET ROLE manual. Inilah konfigurasi yang paling nyaman untuk anggota tim tetap.
Kapan Role Membantu Tim #
| Situasi | Tanpa role | Dengan role |
|---|---|---|
| 10 anggota tim butuh akses sama | 10 rangkaian GRANT identik | 1 role di-GRANT ke 10 akun |
| Kebijakan berubah (tambah/kuurangi hak) | 10 pembaruan manual, rawan miss | 1 GRANT/REVOKE ke role |
| Anggota pindah peran | REVOKE semua hak lama satu per satu | REVOKE role lama, GRANT role baru |
| Auditor sementara 2 minggu | Duplikasi hak permanen | GRANT role, REVOKE saat selesai |
Baris terakhir adalah pola favorit auditor: role app_readonly yang kita buat bisa dipinjamkan ke akun auditor kapan pun lalu dicabut begitu selesai — tanpa pernah menyentuh hak anggota tim lain.
Menjatuhkan Role dan Membersihkan Latihan #
Penutup siklus: mencabut keanggotaan lalu menjatuhkan wadahnya. Semua dari terminal administrator:
-- @test-bash
mysql -h 127.0.0.1 -P 3306 -u root -t -e "REVOKE 'app_dev' FROM 'budi_analyst'@'%'; SHOW GRANTS FOR 'budi_analyst'@'%';"
mysql -h 127.0.0.1 -P 3306 -u root -e "DROP ROLE 'app_dev', 'app_readonly'; DROP USER IF EXISTS 'budi_analyst'@'%'; DROP DATABASE IF EXISTS belajar_aman;"Output yang Dihasilkan:
+------------------------------------------+
| Grants for budi_analyst@% |
+------------------------------------------+
| GRANT USAGE ON *.* TO `budi_analyst`@`%` |
+------------------------------------------+REVOKE 'app_dev' FROM ... melepas keanggotaan — SHOW GRANTS kembali hanya menampilkan GRANT USAGE, bukti hak role tidak pernah melekat langsung. DROP ROLE lalu menghapus wadahnya dari mysql.user; role yang dijatuhkan otomatis lenyap juga dari akun-akun penerimanya, jadi urutan pembersihan ini aman bahkan bila masih ada penerima yang terlewat. Terakhir, akun latihan dan database-nya kita jatuhkan.
Referensi #
- Using Roles — MySQL 8.4 Reference Manual
- CREATE ROLE Statement — MySQL 8.4 Reference Manual
- GRANT Statement — MySQL 8.4 Reference Manual
Role menuntaskan lapisan otorisasi — siapa boleh melakukan apa. Tetapi sebelum GRANT mana pun dievaluasi, server harus lebih dulu memastikan siapa yang menelepon benar-benar pemilik akun itu. Proses pembuktian identitas tersebut — plugin autentikasi, hash kata sandi, dan aturan pencocokan host — adalah chapter berikutnya.