Middleware EnsureTokenIsValid sebelumnya memeriksa satu nilai tetap — bekerja, tetapi tidak bisa dipakai ulang untuk kebutuhan lain. Bayangkan middleware pemeriksa peran: logika “cocokkan peran pengguna” sama untuk semua route, hanya peran yang diminta berbeda — admin di satu route, editor di route lain. Laravel mengatasi ini dengan parameter middleware: route mengirim argumen lewat sintaks role:admin, dan nilai tersebut diterima method handle sebagai parameter biasa. Satu class middleware pun melayani banyak aturan berbeda.
Menerima Parameter di Method handle #
Parameter dikirim setelah tanda titik dua, lalu muncul di handle setelah $next:
// app/Http/Middleware/EnsureRole.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureRole
{
public function handle(Request $request, Closure $next, string $role, ?string $permission = null): Response
{
$user = $request->user();
if (is_null($user) || $user->role !== $role) {
abort(403, 'Peran '.$role.' dibutuhkan.');
}
if (! is_null($permission) && ! in_array($permission, config('roles.permissions.'.$role, []))) {
abort(403, 'Peran '.$role.' tidak memiliki permission '.$permission.'.');
}
return $next($request);
}
}Mari kita bedah alurnya:
string $role— parameter pertama setelah$next; nilainya berasal dari teks setelah titik dua pada route.?string $permission = null— parameter kedua yang opsional; middleware tetap sah dipanggil tanpanya.$user->role !== $role— membandingkan kolomrolemilik pengguna login dengan peran yang diminta route; tamu tanpa login juga ditolak karena$request->user()bernilainull.config('roles.permissions.'.$role, [])— daftar permission per peran dibaca dari file config, bukan ditanam di kode.
Satu hal penting soal tipe: seluruh parameter middleware datang sebagai string, apapun bentuk penulisannya di route. Nilai role:admin menghasilkan $role berisi 'admin'; middleware throttle bawaan pun menerima throttle:60,1 sebagai dua string yang kemudian dihitung secara numerik di dalam handle. Konversi juga menjadi tempat yang tepat untuk menolak parameter tidak sah — misalnya memang abort(500, ...) ketika peran yang diminta tidak ada di config sama sekali.
Tanpa parameter, kita harus menulis satu class per peran: EnsureAdmin, EnsureEditor, EnsurePembayaran, dan seterusnya — semuanya berisi logika kembar yang hanya berbeda satu pembanding. Parameter membalik situasinya: satu class, banyak aturan, dan aturan baru tidak memerlukan composer dump-autoload apalagi deployment ulang.
Parameter di Sisi Route #
Berikut tiga route yang memakai kombinasi parameter berbeda pada middleware yang sama (alias role didaftarkan di bootstrap/app.php pada chapter sebelumnya):
Route::get('/admin/laporan', fn () => 'Laporan admin')
->middleware('role:admin');
Route::post('/admin/artikel', fn () => 'Artikel dibuat')
->middleware('role:editor,create');
Route::post('/admin/artikel/hapus', fn () => 'Artikel dihapus')
->middleware('role:editor,delete');Tanda koma memisahkan parameter kedua: role:editor,create berarti $role = 'editor' dan $permission = 'create'. Nilai keduanya berupa string — bila butuh angka, lakukan konversi di dalam middleware.
Daftar Permission dari Config #
Pemeriksaan permission tidak boleh ditanam permanen di kode karena daftarnya berubah mengikuti bisnis. Kita pindahkan ke config/roles.php:
return [
'permissions' => [
'admin' => ['view', 'create', 'update', 'delete'],
'editor' => ['view', 'create', 'update'],
],
];Dengan tabel ini, peran admin boleh menghapus artikel, sedangkan editor tidak — dan menambah peran baru cukup dengan menambah baris config. Skema users kita memakai kolom role bertipe string untuk mengikat pengguna ke peran tersebut.
Pemisahan ini juga menguntungkan tim operasional. Daftar permission bisa disesuaikan dengan kebijakan terbaru tanpa menyentuh kode aplikasi, dan lingkungan staging boleh membawa daftar berbeda dari produksi lewat file config yang terpisah. Batasnya juga jelas: config dibaca saat request berjalan, sehingga perubahan berlaku seketika — tidak seperti cache route yang butuh pembersihan terpisah.
Menguji Izin dan Penolakan #
Empat skenario berikut menguji seluruh cabang logika: admin di route admin, editor yang salah peran, editor dengan permission yang cocok, dan editor tanpa permission yang dibutuhkan. Setiap test membuat pengguna lewat factory dengan kolom role eksplisit, lalu actingAs() mensimulasikan keadaan login sebagai pengguna tersebut — sehingga $request->user() di dalam middleware terisi tanpa perlu proses login sungguhan:
public function test_editor_ditolak_di_route_admin(): void
{
$siti = User::factory()->create([
'email' => '[email protected]',
'role' => 'editor',
]);
$response = $this->actingAs($siti)->get('/admin/laporan');
$response->assertStatus(403);
echo 'editor_buka_laporan: status=', $response->getStatusCode(),
', pesan="', $response->exception->getMessage(), '"', PHP_EOL;
}
public function test_editor_tanpa_permission_delete_ditolak(): void
{
$siti = User::factory()->create([
'email' => '[email protected]',
'role' => 'editor',
]);
$response = $this->actingAs($siti)->post('/admin/artikel/hapus');
$response->assertStatus(403);
echo 'editor_hapus_artikel: status=', $response->getStatusCode(),
', pesan="', $response->exception->getMessage(), '"', PHP_EOL;
}Output yang Dihasilkan:
admin_buka_laporan: status=200, isi="Laporan admin"
editor_buka_laporan: status=403, pesan="Peran admin dibutuhkan."
editor_buat_artikel: status=200, isi="Artikel dibuat"
editor_hapus_artikel: status=403, pesan="Peran editor tidak memiliki permission delete."
tamu_buka_laporan: status=403, pesan="Peran admin dibutuhkan."Baris editor_buat_artikel membuktikan parameter kedua bekerja: create tercantum di config untuk peran editor, sehingga request lolos ke controller. Bandingkan dengan baris terakhirnya sendiri — route yang sama di tengah admin ditolak karena peran tidak cocok.
Dengan parameter, satu class middleware menangani seluruh variasi aturan peran. Pola ini juga dipakai middleware bawaan seperti auth:admin (guard) dan throttle:60,1 (limit dan menit) — daftar lengkapnya ada di dokumentasi middleware
. Selanjutnya kita membedah middleware autentikasi bawaan dari sisi pembuatnya, lalu membangun versi sederhananya sendiri.