RBAC (Role-Based Access Control) memutuskan izin dari role: kamu guru, jadi boleh mengisi nilai. ABAC (Attribute-Based Access Control) memutuskan dari atribut: data tentang user, tentang data yang diminta, dan tentang situasinya. Bayangin satpam sekolah. Seragam guru (role) membuat Pak Andi boleh masuk ruang guru. Tapi untuk membuka lemari nilai IPA, satpam juga melihat hal lain: Pak Andi mengajar mapel apa, lemari itu milik mapel apa, dan sekarang jam berapa. Seragam saja gak cukup.
🧪 Tebakan cepat: tabel izin Portal Sekolah punya aturan "
studentbolehgrades:read". Siti membukaGET /grades/41, nilai milik Rizky. Lolos atau ditolak? Tebak dulu, jawabannya ada di bagian "Role saja gak cukup".
Role saja gak cukup
Lesson Role-based Access Control sudah membangun RBAC: tiap role punya daftar permission, dan role bisa mewarisi role lain. Di lesson ini datanya bertambah. Tiap nilai jadi satu baris di tabel grades, dan ada satu user baru di cerita, Rizky, alumni yang akunnya sudah dibekukan:
| User | role |
department |
blocked |
|---|---|---|---|
| Siti (id 1) | student |
gak | |
| Pak Andi (id 2) | teacher |
Matematika |
gak |
| Bu Dewi (id 3) | admin |
gak | |
| Rizky (id 4) | student |
ya |
| Nilai | Milik (studentId) |
Mapel (subject) |
|---|---|---|
#31 |
Siti (1) | Matematika |
#32 |
Siti (1) | IPA |
#41 |
Rizky (4) | Matematika |
RBAC cuma bertanya "role-nya punya grades:read?". Siti student, jadi jawabannya ya, dan nilai Rizky terbuka. Jadi tebakannya: lolos, dan itu bolong. Role gak tahu nilai itu milik siapa.
Atribut: pemilik, mapel, jam
ABAC menambahkan syarat yang membaca tiga jenis atribut:
- Atribut user:
role,department(mapel yang diajar),blocked. - Atribut resource, data yang diminta:
studentId(pemiliknya),subject. - Atribut lingkungan: situasi saat request datang, misalnya jam di server.
Aturan-aturannya ditulis jadi tabel policy. Ini tabel yang dipakai Lab, dengan semua syarat nyala:
| # | Efek | Siapa | Aksi | Syarat |
|---|---|---|---|---|
| R1 | deny | siapa pun | apa pun | user.blocked |
| R2 | allow | student |
grades:read |
grade.studentId === user.id (pemilik) |
| R3 | allow | teacher |
grades:read |
|
| R4 | allow | teacher |
grades:write |
grade.subject === user.department dan jam 07.00 sampai 16.59 |
| R5 | allow | admin |
apa pun |
Cara tabelnya dievaluasi
Dua aturan main:
- Deny by default: kalau gak ada aturan allow yang cocok, jawabannya ditolak. Lupa menulis aturan berarti tertutup, bukan terbuka.
- Deny menang: kalau ada aturan deny yang cocok, ditolak, walau ada allow yang juga cocok.
function decide(user, action, grade, env) {
const matches = rules.filter((r) =>
(!r.role || r.role === user.role) &&
(!r.action || r.action === action) &&
(!r.when || r.when(user, grade, env))
);
if (matches.some((r) => r.effect === "deny")) return "deny"; // deny menang
if (matches.some((r) => r.effect === "allow")) return "allow";
return "deny"; // gak ada yang cocok: deny by default
}
- Aturan tanpa
roleatauactionberlaku untuk semua role atau semua aksi, seperti R1 dan R5. whenadalah syaratnya. Aturan yang syaratnya gagal dianggap gak cocok, jadi bukan allow.
Hasilnya di jam 10.00, persis seperti di Lab:
| Siapa | Request | Hasil | Diputuskan oleh |
|---|---|---|---|
| Siti | GET /grades/31 |
200 |
R2: dia pemiliknya |
| Siti | GET /grades/41 |
403 |
R2 gagal syarat pemilik, gak ada allow lain: deny by default |
| Pak Andi | PUT /grades/31 |
200 |
R4: Matematika, jam 10 |
| Pak Andi | PUT /grades/32 |
403 |
R4 gagal syarat mapel: deny by default |
| Bu Dewi | DELETE /users/4 |
204 |
R5 |
| Rizky | GET /grades/41 |
403 |
R1: diblokir, deny menang atas R2 |
Geser jam server ke 19.00, dan PUT /grades/31 dari Pak Andi ikut jadi 403, karena syarat jamnya gagal.
Urutan cek di latihan
Latihannya memakai contoh kantor, bukan sekolah, tapi polanya sama: role admin (read, write, delete), editor (read, write), viewer (read), resource "confidential-file" yang cuma untuk department "hr", dan atribut isBlocked. Kalau ditulis sebagai if berurutan, urutannya menentukan hasil:
isBlocked === true: langsungfalse. Ini R1, deny yang menang atas segalanya.resource === "confidential-file"dan department-nya bukan"hr":false, walau role-nyaadmin.- Baru tabel role:
(ROLE_ACTIONS[role] || []).includes(action). Role yang gak dikenal dapat daftar kosong, jadi ditolak.
Kalau tabel role dicek duluan dan langsung return true, admin yang diblokir dan admin dari engineering lolos. Editor dari hr yang mau delete tetap ditolak di langkah 3, karena lolos syarat atribut bukan berarti dapat izin.
Di mana cek-nya tinggal
Di server, sebelum controller bekerja. Di Laravel tempatnya Policy:
class GradePolicy
{
public function before(User $user): ?bool
{
return $user->blocked ? false : null; // R1: false = tolak semua, null = lanjut
}
public function view(User $user, Grade $grade): bool
{
return match ($user->role) {
'student' => $grade->student_id === $user->id, // R2
'teacher', 'admin' => true, // R3, R5
default => false, // deny by default
};
}
public function update(User $user, Grade $grade): bool
{
if ($user->role === 'admin') {
return true; // R5
}
return $user->role === 'teacher'
&& $grade->subject === $user->department // R4: mapel
&& now()->hour >= 7 && now()->hour < 17; // R4: jam sekolah
}
}
Route::put('/grades/{grade}', [GradeController::class, 'update'])->can('update', 'grade');
beforejalan sebelum method lain. Mengembalikanfalsemenolak semuanya,nullberarti lanjut keviewatauupdate.->can('update', 'grade')memasang middleware yang memanggilGradePolicy::updatedengan nilai dari URL, lalu menjawab403kalau ditolak. Di dalam controller, bisa jugaGate::authorize('update', $grade).- Di view,
@can('update', $grade)cuma menyembunyikan tombol "Edit nilai". Itu tampilan, bukan pengaman: request tetap bisa dikirim tanpa tombol.
RBAC, ABAC, atau keduanya
| RBAC | ABAC | |
|---|---|---|
| Memutuskan dari | role | atribut user, resource, lingkungan |
| Contoh | guru boleh mengisi nilai | guru boleh mengisi nilai mapelnya, di jam sekolah |
| Kelebihan | sederhana, gampang diaudit | bisa menulis aturan apa pun |
| Harganya | gak tahu data milik siapa | aturannya lebih banyak dan perlu dites |
Kebanyakan aplikasi memakai keduanya: role untuk izin kasar, atribut untuk yang halus. Di Laravel, paket Spatie Laravel Permission menyimpan role dan permission di database, jadi admin bisa menambah atau mengubah role lewat panel tanpa deploy ($user->assignRole('teacher'), $user->hasRole('teacher')). Gate dan Policy bawaan menulis aturan di kode, cocok untuk logika atribut yang jarang berubah. Keduanya bisa digabung: Spatie menjawab role-nya apa, Policy mengecek pemilik, mapel, dan jam.
⚠️ Jebakan yang sering ketemu
- Default allow.
return truedi akhir function izin membuat aturan yang lupa ditulis jadi terbuka. Akhiri dengan tolak. - Cek izin cuma di view.
@canatau tombol yang disembunyikan gak menghentikan request. Policy dan->can(...)yang menghentikannya. - Urutan cek terbalik. Tabel role yang
return truelebih dulu melewatkan blokir dan syarat atribut. - Jam dari client. Atribut lingkungan seperti jam dibaca dari server (
now()), bukan dari data yang dikirim browser.
TL;DR: RBAC memutuskan dari role, ABAC dari atribut user, resource, dan lingkungan (pemilik, mapel, jam). Tulis aturannya sebagai tabel policy: deny by default, dan deny menang atas allow. Di latihan urutannya: blokir, lalu syarat atribut, baru tabel role. Cek izin tinggal di server, di Policy Laravel dan ->can() di route, bukan di view. Spatie untuk role yang dikelola lewat database, Policy untuk logika atribut, dan keduanya bisa dipakai bersama.