RBAC vs ABAC: Implementasi — Auth

Dua model otorisasi paling populer: RBAC (Role-Based Access Control) dan ABAC (Attribute-Based Access Control). Memahami kapan memakai yang mana adalah kunci…

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 "student boleh grades:read". Siti membuka GET /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:

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:

  1. Deny by default: kalau gak ada aturan allow yang cocok, jawabannya ditolak. Lupa menulis aturan berarti tertutup, bukan terbuka.
  2. 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
}

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:

  1. isBlocked === true: langsung false. Ini R1, deny yang menang atas segalanya.
  2. resource === "confidential-file" dan department-nya bukan "hr": false, walau role-nya admin.
  3. 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');

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

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.

Yang akan kamu pelajari