BAB 14: Authorization
Mengontrol akses dengan Gates dan Policies untuk membatasi tindakan pengguna di Laravel.
Setelah sistem login berjalan, aplikasi catatan kita tahu siapa yang sedang menggunakannya. Tapi pertanyaan “siapa yang boleh melakukan apa” belum terjawab. Saat ini, semua pengguna yang login bisa mengakses, mengubah, bahkan menghapus catatan siapapun — cukup dengan menebak URL-nya.
Authorization adalah lapisan yang menjawab pertanyaan itu. Bukan tentang apakah pengguna sudah login, melainkan tentang apakah pengguna yang login ini berhak melakukan tindakan tertentu.
Gates vs Policies
Laravel menyediakan dua mekanisme authorization: Gates dan Policies. Keduanya melakukan hal yang sama, tapi di konteks yang berbeda.
| Gates | Policies | |
|---|---|---|
| Bentuk | Closure sederhana | Class terstruktur |
| Cocok untuk | Pengecekan sederhana, non-model | Operasi CRUD pada model |
| Lokasi definisi | AppServiceProvider | File terpisah di app/Policies/ |
| Contoh kasus | ”Apakah user adalah admin?" | "Apakah user boleh mengedit catatan ini?” |
Untuk model Catatan yang kita punya, Policies adalah pilihan yang tepat karena semua tindakan berputar di sekitar model tersebut.
Membuat Policy
Gunakan Artisan untuk membuat policy:
php artisan make:policy CatatanPolicy --model=Catatan
Flag --model=Catatan membuat Laravel sekaligus membuat stub untuk method-method CRUD standar: viewAny, view, create, update, delete, restore, forceDelete.
File policy akan muncul di app/Policies/CatatanPolicy.php. Isi dengan logika kepemilikan:
<?php
// app/Policies/CatatanPolicy.php
namespace App\Policies;
use App\Models\Catatan;
use App\Models\User;
class CatatanPolicy
{
public function viewAny(User $user): bool
{
return true; // semua pengguna yang login boleh melihat daftarnya
}
public function view(User $user, Catatan $catatan): bool
{
return $user->id === $catatan->user_id;
}
public function create(User $user): bool
{
return true; // semua pengguna yang login boleh membuat catatan baru
}
public function update(User $user, Catatan $catatan): bool
{
return $user->id === $catatan->user_id;
}
public function delete(User $user, Catatan $catatan): bool
{
return $user->id === $catatan->user_id;
}
}
Polanya konsisten: untuk tindakan yang melibatkan instance model (view, update, delete), kita bandingkan $user->id dengan $catatan->user_id. Untuk tindakan yang tidak melibatkan instance model (viewAny, create), method hanya menerima $user.
Laravel secara otomatis menemukan policy yang cocok untuk model berdasarkan konvensi penamaan — Catatan model dipasangkan ke CatatanPolicy. Tidak perlu registrasi manual selama keduanya ada di folder yang benar (app/Models/ dan app/Policies/).
Menggunakan Policy di Controller
Ada dua cara menggunakan policy di controller. Cara pertama dengan $this->authorize(), yang tersedia di semua controller yang extend Controller:
<?php
// app/Http/Controllers/CatatanController.php
namespace App\Http\Controllers;
use App\Models\Catatan;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class CatatanController extends Controller
{
public function index(): View
{
$catatan = auth()->user()->catatan()->latest()->paginate(10);
return view('catatan.index', compact('catatan'));
}
public function show(Catatan $catatan): View
{
$this->authorize('view', $catatan);
return view('catatan.show', compact('catatan'));
}
public function edit(Catatan $catatan): View
{
$this->authorize('update', $catatan);
return view('catatan.edit', compact('catatan'));
}
public function update(Request $request, Catatan $catatan): RedirectResponse
{
$this->authorize('update', $catatan);
$data = $request->validate([
'judul' => 'required|string|min:3|max:200',
'isi' => 'required|string|min:10',
'prioritas' => 'required|in:rendah,sedang,tinggi',
]);
$catatan->update($data);
return redirect()->route('catatan.show', $catatan)
->with('sukses', 'Catatan berhasil diperbarui.');
}
public function destroy(Catatan $catatan): RedirectResponse
{
$this->authorize('delete', $catatan);
$catatan->delete();
return redirect()->route('catatan.index')
->with('sukses', 'Catatan berhasil dihapus.');
}
}
Ketika $this->authorize() menemukan bahwa pengguna tidak berhak, Laravel otomatis mengembalikan response HTTP 403 Forbidden. Kita tidak perlu menulis if dan abort(403) secara manual.
Cara kedua adalah menggunakan Gate::authorize() dari Illuminate\Support\Facades\Gate — ini berguna di luar controller, misalnya di service class.
Policy di Blade Template
Authorization juga bisa diperiksa langsung di template Blade menggunakan direktif @can dan @cannot:
{{-- resources/views/catatan/show.blade.php --}}
<x-app-layout>
<div class="max-w-2xl mx-auto mt-8">
<h1 class="text-2xl font-bold">{{ $catatan->judul }}</h1>
<p class="mt-4 text-gray-700">{{ $catatan->isi }}</p>
@can('update', $catatan)
<div class="mt-6 flex gap-3">
<a href="{{ route('catatan.edit', $catatan) }}"
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">
Edit
</a>
</div>
@endcan
@can('delete', $catatan)
<form method="POST" action="{{ route('catatan.destroy', $catatan) }}"
class="inline">
@csrf
@method('DELETE')
<button type="submit"
class="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
onclick="return confirm('Hapus catatan ini?')">
Hapus
</button>
</form>
@endcan
</div>
</x-app-layout>
Tombol edit dan hapus hanya muncul jika pengguna yang sedang login adalah pemilik catatan tersebut. Ini adalah authorization di layer tampilan — mencegah pengguna melihat aksi yang memang tidak bisa mereka lakukan.
Menyembunyikan tombol di Blade bukan pengganti $this->authorize() di controller. Pengguna masih bisa mengirim request langsung tanpa melalui UI. Selalu periksa authorization di controller.
Gates untuk Kasus Sederhana
Untuk pengecekan yang tidak terikat pada model tertentu — misalnya memastikan pengguna adalah admin — Gates lebih ringkas. Daftarkan di AppServiceProvider:
<?php
// app/Providers/AppServiceProvider.php
namespace App\Providers;
use App\Models\User;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
Gate::define('akses-admin', function (User $user) {
return $user->role === 'admin';
});
}
}
Gunakan di controller atau Blade:
// Di controller
Gate::authorize('akses-admin');
// Atau dengan if manual
if (Gate::denies('akses-admin')) {
abort(403, 'Halaman ini hanya untuk admin.');
}
@can('akses-admin')
<a href="/admin">Panel Admin</a>
@endcan
Gate before memungkinkan kita membuat “super user” yang bisa melewati semua pengecekan:
// app/Providers/AppServiceProvider.php
Gate::before(function (User $user, string $ability) {
if ($user->role === 'superadmin') {
return true;
}
});
Ketika Gate::before mengembalikan true, semua pengecekan gate dan policy lainnya dilewati untuk pengguna tersebut.
Latihan
Coba kerjakan pengembangan berikut:
-
Policy dengan kondisi tambahan — Tambahkan method
forceDeletekeCatatanPolicyyang hanya mengizinkan pengguna denganrole === 'admin'untuk menghapus catatan permanen (bukan soft delete). Gunakan di route baru/catatan/{id}/force-delete. -
Pesan error kustom — Ubah method
updatedandeletediCatatanPolicyagar mengembalikanResponse::deny('...')dengan pesan yang spesifik, bukan sekadarfalse. Tampilkan pesan tersebut di view menggunakanGate::inspect(). -
Gate untuk pembatasan jumlah — Buat gate
buat-catatanyang menolak pembuatan catatan baru jika pengguna sudah memiliki lebih dari 10 catatan aktif. Integrasikan denganCatatanController@create.
Penutup Bab
Gates dan Policies melengkapi sistem autentikasi yang kita bangun di bab sebelumnya. Authentication menjawab “siapa kamu”, authorization menjawab “apa yang boleh kamu lakukan”. Keduanya bekerja bersama untuk memastikan setiap tindakan di aplikasi hanya bisa dilakukan oleh pihak yang berhak.
Dengan dua lapisan ini terpasang, fondasi aplikasi kita sudah cukup kokoh. Langkah selanjutnya adalah berkenalan dengan Artisan — CLI bawaan Laravel yang sudah kita gunakan beberapa kali sejak awal, tapi belum pernah kita telusuri kemampuannya secara penuh.