Autentikasi dan Role Permission di Laravel dengan Spatie

Aplikasi bisnis jarang punya satu jenis pengguna. Kasir hanya boleh mencatat transaksi, manajer boleh melihat laporan, dan admin boleh menghapus data. Kalau pembedaan ini ditulis d...

Autentikasi dan Role Permission di Laravel dengan Spatie

Aplikasi bisnis jarang punya satu jenis pengguna. Kasir hanya boleh mencatat transaksi, manajer boleh melihat laporan, dan admin boleh menghapus data. Kalau pembedaan ini ditulis dengan if ($user->role == 'admin') di mana-mana, setiap perubahan aturan berarti mencari dan mengedit puluhan file, dan hampir pasti ada yang terlewat.

Tutorial ini menyusun sistem akses yang rapi di Laravel 11: autentikasi dengan Laravel Breeze, lalu role dan permission dengan paket spatie/laravel-permission (versi 6). Kita bahas desain aksesnya dulu, baru kode, lalu error yang biasanya muncul di produksi.

1. Autentikasi vs Otorisasi

Dua istilah ini sering tertukar:

  • Autentikasi menjawab "siapa Anda?". Ini urusan login, logout, reset password, dan verifikasi email.
  • Otorisasi menjawab "Anda boleh melakukan apa?". Ini urusan role, permission, dan policy.

Laravel sudah menyediakan fondasi autentikasi. Spatie mengurus bagian otorisasi tanpa menggantikan sistem Gate bawaan Laravel, sehingga $user->can() dan @can tetap bekerja seperti biasa.

2. Memasang Autentikasi dengan Breeze

Untuk proyek baru, Breeze adalah titik awal paling ringan. Ia membuat halaman login, register, reset password, dan profil:

composer require laravel/breeze --dev
php artisan breeze:install blade
php artisan migrate
npm install && npm run build

Setelah itu route /login dan /register sudah aktif dan middleware auth siap dipakai. Untuk aplikasi internal seperti POS atau HRIS, pertimbangkan menonaktifkan register publik dan membuat akun hanya dari panel admin.

3. Rancang Akses Sebelum Menulis Kode

Langkah yang paling sering dilewati adalah membuat matriks akses. Tulis daftar aksi, lalu tentukan role mana yang boleh. Contoh untuk aplikasi kasir sederhana:

PermissionKasirManajerAdmin
transaksi.buatYaYaYa
transaksi.batalTidakYaYa
laporan.lihatTidakYaYa
produk.kelolaTidakYaYa
pengguna.kelolaTidakTidakYa

Pola nama objek.aksi membuat daftar permission mudah dibaca dan diurutkan. Yang penting konsisten: jangan campur "edit articles" dengan "artikel.hapus".

4. Instalasi spatie/laravel-permission

composer require spatie/laravel-permission
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan optimize:clear
php artisan migrate

Migration membuat lima tabel: roles, permissions, model_has_roles, model_has_permissions, dan role_has_permissions. Lalu tambahkan trait ke model User:

use Illuminate\Foundation\Auth\User as Authenticatable;
use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
    use HasRoles;
}

Trait ini menambahkan method seperti assignRole(), hasRole(), givePermissionTo(), dan syncRoles().

5. Seeder: Satu Sumber Kebenaran

Jangan membuat role lewat tinker di server produksi. Tulis matriks akses tadi sebagai seeder yang aman dijalankan berulang:

use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Spatie\Permission\PermissionRegistrar;

class RolePermissionSeeder extends Seeder
{
    public function run(): void
    {
        app(PermissionRegistrar::class)->forgetCachedPermissions();

        $matrix = [
            'kasir'   => ['transaksi.buat'],
            'manajer' => ['transaksi.buat', 'transaksi.batal', 'laporan.lihat', 'produk.kelola'],
        ];

        $all = collect($matrix)->flatten()->push('pengguna.kelola')->unique();

        foreach ($all as $name) {
            Permission::firstOrCreate(['name' => $name, 'guard_name' => 'web']);
        }

        foreach ($matrix as $role => $perms) {
            Role::firstOrCreate(['name' => $role, 'guard_name' => 'web'])
                ->syncPermissions($perms);
        }

        Role::firstOrCreate(['name' => 'admin', 'guard_name' => 'web']);
    }
}

syncPermissions() mengganti seluruh permission role sesuai array, jadi jika Anda mencabut izin dari matriks, menjalankan ulang seeder ikut mencabutnya di database. Role admin sengaja dibiarkan tanpa permission karena akan ditangani oleh Gate::before.

6. Super-Admin dengan Gate::before

Daripada memberi admin setiap permission satu per satu (dan lupa saat permission baru ditambah), daftarkan pengecualian di AppServiceProvider:

use Illuminate\Support\Facades\Gate;

public function boot(): void
{
    Gate::before(function ($user, $ability) {
        return $user->hasRole('admin') ? true : null;
    });
}

Kembalikan null, bukan false, untuk pengguna lain. Nilai false akan menolak semua pengecekan untuk non-admin.

7. Melindungi Route dengan Middleware

Di Laravel 11, daftarkan alias middleware Spatie di bootstrap/app.php:

->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'role'               => \Spatie\Permission\Middleware\RoleMiddleware::class,
        'permission'         => \Spatie\Permission\Middleware\PermissionMiddleware::class,
        'role_or_permission' => \Spatie\Permission\Middleware\RoleOrPermissionMiddleware::class,
    ]);
})

Lalu gunakan di route:

Route::middleware('auth')->group(function () {
    Route::post('/transaksi', [TransaksiController::class, 'store'])
        ->middleware('permission:transaksi.buat');

    Route::get('/laporan', [LaporanController::class, 'index'])
        ->middleware('permission:laporan.lihat');

    Route::resource('pengguna', UserController::class)
        ->middleware('permission:pengguna.kelola');
});

Perhatikan bahwa Gate::before tidak berlaku untuk middleware role:, karena middleware itu mengecek nama role secara langsung. Middleware permission: memakai Gate, sehingga admin tetap lolos. Ini satu alasan lagi untuk mengecek permission, bukan role.

8. Mengecek Akses di Controller dan Blade

// di controller
public function destroy(Transaksi $transaksi)
{
    $this->authorize('transaksi.batal');   // butuh trait AuthorizesRequests
    // atau: abort_unless(auth()->user()->can('transaksi.batal'), 403);

    $transaksi->update(['status' => 'batal']);

    return back();
}

Catatan: controller dasar Laravel 11 tidak lagi menyertakan trait AuthorizesRequests. Tambahkan trait itu ke app/Http/Controllers/Controller.php atau pakai Gate::authorize('transaksi.batal').

@can('laporan.lihat')
    <a href="{{ route('laporan.index') }}">Laporan</a>
@endcan

Menyembunyikan tombol di Blade hanya soal tampilan. Pengamanan sebenarnya tetap di route atau controller, karena pengguna bisa memanggil URL langsung.

9. Permission Ditambah Policy

Permission menjawab "boleh membatalkan transaksi atau tidak". Ia tidak menjawab "boleh membatalkan transaksi milik cabang lain atau tidak". Untuk aturan yang bergantung pada data, gabungkan dengan Policy:

public function delete(User $user, Transaksi $trx): bool
{
    return $user->can('transaksi.batal')
        && $trx->cabang_id === $user->cabang_id;
}

10. Error Umum dan Solusinya

  • "There is no permission named X for guard web": nama permission salah ketik atau belum di-seed. Jalankan seeder dan cek ejaan.
  • Perubahan izin tidak berlaku: cache permission. Jalankan php artisan permission:cache-reset setelah mengubah data.
  • Middleware tidak dikenal: alias belum didaftarkan di bootstrap/app.php, atau namespace yang ditulis versi lama (Middlewares di v5, Middleware di v6).
  • Akses API selalu 403: permission dibuat untuk guard web sementara request memakai guard lain. Samakan guard_name atau buat permission untuk guard tersebut.

11. Membuat Halaman Kelola Role untuk Admin

Setelah aplikasi berjalan, pemilik bisnis biasanya ingin mengatur sendiri siapa mendapat role apa tanpa memanggil developer. Halaman pengelolaan pengguna cukup memanggil syncRoles():

public function updateRole(Request $request, User $user)
{
    Gate::authorize('pengguna.kelola');

    $data = $request->validate([
        'role' => ['required', 'exists:roles,name'],
    ]);

    if ($user->is($request->user())) {
        return back()->with('error', 'Anda tidak bisa mengubah role akun sendiri.');
    }

    $user->syncRoles([$data['role']]);

    return back()->with('success', 'Role berhasil diperbarui.');
}

Ada dua pengaman di kode ini. Validasi exists:roles,name mencegah role palsu dikirim lewat form, dan pengecekan akun sendiri mencegah admin tanpa sengaja menurunkan dirinya lalu terkunci dari panel. Untuk aplikasi yang lebih sensitif, catat juga setiap perubahan role ke tabel log berisi siapa yang mengubah, untuk siapa, dan kapan.

Sebaiknya permission tetap dikelola lewat seeder oleh developer, sementara admin hanya memilih role. Jika admin juga bisa membuat permission bebas dari panel, nama permission di database bisa tidak cocok dengan yang dicek di kode, dan fitur menjadi tidak bisa diakses tanpa ada pesan error yang jelas.

Mengujinya

Test berikut memastikan matriks akses benar-benar diterapkan. Jalankan seeder di dalam test agar role dan permission tersedia:

it('kasir tidak bisa membuka laporan', function () {
    $this->seed(RolePermissionSeeder::class);
    $kasir = User::factory()->create()->assignRole('kasir');

    $this->actingAs($kasir)->get('/laporan')->assertForbidden();
});

it('manajer bisa membuka laporan', function () {
    $this->seed(RolePermissionSeeder::class);
    $manajer = User::factory()->create()->assignRole('manajer');

    $this->actingAs($manajer)->get('/laporan')->assertOk();
});

Checklist Implementasi

  • Matriks akses tertulis dan disetujui pemilik bisnis.
  • Semua role dan permission dibuat lewat seeder, bukan manual.
  • Kode mengecek permission, bukan nama role.
  • Setiap route sensitif punya middleware, bukan hanya tombol yang disembunyikan.
  • Ada test yang memastikan role kasir mendapat 403 di halaman laporan.

Untuk lapisan keamanan lain, lihat checklist keamanan dasar Laravel dan 10 best practice keamanan aplikasi web. Jika aplikasi Anda melayani banyak klien sekaligus, desain role per tenant dibahas di membangun multi-tenant SaaS dengan Laravel.

Ingin langsung mempraktikkannya?

Buat akun gratis untuk mengunduh contoh aplikasi Laravel, menyimpan tutorial favorit, dan mencoba kursus. Gratis, bisa daftar dengan akun Google.

Daftar Gratis Lihat katalog aplikasi
Yudhi
Ditulis oleh
Yudhi
Founder & Lead Developer, GudangCode

Yudhi adalah founder GudangCode dan developer Laravel dengan pengalaman membangun puluhan sistem informasi bisnis siap pakai — mulai dari POS, HRIS, hingga aplikasi manajemen. Ia menulis panduan dan artikel di GudangCode untuk membantu developer Indonesia menjalankan, memahami, dan men-deploy source code Laravel dengan benar.

Laravel PHP MySQL Sistem Informasi Bisnis
Lihat semua artikel Yudhi
Laravel Spatie Role Permission Keamanan
Bacaan Paling Dicari
📚 Free Learning Hub

Learn Coding for Free at DhieCoderWeb

Explore Laravel, PHP, JavaScript tutorials, source code, web development guides, and practical programming tips.

DhieCoderWeb
100+
Tutorials
Free
Learning
SEO
Tips
Visit Dhiecoderweb.com →