BAB 35: Proteksi Route dengan Auth
Mengamankan rute aplikasi agar hanya dapat diakses oleh pengguna yang terautentikasi.
Aplikasi manajemen catatan sudah berfungsi lengkap. CRUD berjalan, paginasi bekerja, data bisa ditambah, diedit, dan dihapus. Tapi ada satu lubang yang belum ditutup: siapa pun bisa mengakses /catatan tanpa perlu login. Buka browser dalam mode incognito, ketik URL-nya langsung — semua data muncul, semua tombol bisa diklik.
Ini bukan sekadar masalah keamanan teknis. Ini tentang logika aplikasi yang seharusnya: catatan adalah data pribadi. Hanya pengguna yang sudah terautentikasi yang boleh melihat dan mengelolanya.
Middleware auth dan Cara Kerjanya
Laravel sudah menyertakan middleware auth secara bawaan. Middleware ini sangat sederhana: periksa apakah request berasal dari pengguna yang sudah login. Jika ya, request diteruskan ke controller. Jika tidak, pengguna di-redirect ke route bernama login.
Yang perlu dilakukan: daftarkan route-route catatan di bawah middleware auth, dan buat route login beserta controller-nya.
Membuat LoginController
Jalankan artisan untuk membuat controller:
php artisan make:controller Auth/LoginController
Laravel akan membuat file di app/Http/Controllers/Auth/LoginController.php. Isi controller dengan tiga method:
// app/Http/Controllers/Auth/LoginController.php
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
public function showLoginForm()
{
return view('auth.login');
}
public function login(Request $request)
{
$credentials = $request->validate([
'email' => 'required|email',
'password' => 'required',
]);
if (Auth::attempt($credentials, $request->boolean('remember'))) {
$request->session()->regenerate();
return redirect()->intended(route('catatan.index'));
}
return back()->withErrors([
'email' => 'Email atau password salah.',
])->onlyInput('email');
}
public function logout(Request $request)
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect()->route('login');
}
}
showLoginForm() hanya mengembalikan view formulir login.
login() memvalidasi input, lalu memanggil Auth::attempt(). Method ini mengambil credentials, mencari user dengan email tersebut di database, memverifikasi password menggunakan bcrypt, dan jika cocok — membuat session login. Parameter kedua $request->boolean('remember') mengaktifkan “remember me” cookie jika checkbox dicentang.
Jika login berhasil, redirect()->intended() mengarahkan pengguna ke halaman yang semula ingin mereka buka sebelum diredirect ke login. Misalnya, kalau pengguna mencoba membuka /catatan/5 dan diredirect ke login karena belum autentikasi, setelah login sukses mereka langsung masuk ke /catatan/5 — bukan ke halaman default.
logout() melakukan tiga hal: menghapus data autentikasi dari session (Auth::logout()), menginvalidasi session sepenuhnya, dan meregenerasi CSRF token untuk keamanan.
$request->session()->regenerate() setelah login sukses adalah langkah keamanan penting yang disebut session fixation prevention. Ini memastikan session ID lama yang mungkin sudah diketahui pihak lain tidak bisa dipakai setelah login.
Membuat View Login
Buat folder dan file view untuk halaman login:
mkdir -p resources/views/auth
{{-- resources/views/auth/login.blade.php --}}
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - Manajemen Catatan</title>
@vite('resources/css/app.css')
</head>
<body class="bg-gray-100 min-h-screen flex items-center justify-center">
<div class="bg-white p-8 rounded shadow w-full max-w-md">
<h1 class="text-2xl font-bold text-gray-900 mb-6">Masuk</h1>
@if ($errors->any())
<div class="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded mb-4 text-sm">
{{ $errors->first() }}
</div>
@endif
<form method="POST" action="{{ route('login') }}">
@csrf
<div class="mb-4">
<label for="email" class="block text-sm font-medium text-gray-700 mb-1">Email</label>
<input type="email" id="email" name="email" value="{{ old('email') }}" required autofocus
class="w-full border border-gray-300 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div class="mb-6">
<label for="password" class="block text-sm font-medium text-gray-700 mb-1">Password</label>
<input type="password" id="password" name="password" required
class="w-full border border-gray-300 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div class="flex items-center mb-6">
<input type="checkbox" id="remember" name="remember" class="mr-2">
<label for="remember" class="text-sm text-gray-600">Ingat saya</label>
</div>
<button type="submit"
class="w-full bg-blue-600 text-white py-2 rounded font-medium hover:bg-blue-700">
Masuk
</button>
</form>
</div>
</body>
</html>
View ini sengaja tidak menggunakan layout layouts.app karena halaman login tidak perlu navbar dan tidak perlu session flash message. Ini adalah halaman standalone.
Perhatikan old('email') di input email. Jika login gagal dan form di-submit ulang, nilai email yang sudah diketik sebelumnya akan terisi kembali sehingga pengguna tidak perlu mengetik ulang.
Memperbarui Routes
Sekarang hubungkan semua bagian di routes/web.php. Route catatan dipindahkan ke dalam grup middleware auth, dan route login/logout ditambahkan:
// routes/web.php
<?php
use App\Http\Controllers\Auth\LoginController;
use App\Http\Controllers\CatatanController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
// Route autentikasi
Route::get('/login', [LoginController::class, 'showLoginForm'])->name('login')->middleware('guest');
Route::post('/login', [LoginController::class, 'login'])->middleware('guest');
Route::post('/logout', [LoginController::class, 'logout'])->name('logout')->middleware('auth');
// Route catatan — dilindungi middleware auth
Route::middleware('auth')->group(function () {
Route::get('/catatan', [CatatanController::class, 'index'])->name('catatan.index');
Route::get('/catatan/create', [CatatanController::class, 'create'])->name('catatan.create');
Route::post('/catatan', [CatatanController::class, 'store'])->name('catatan.store');
Route::get('/catatan/{catatan}', [CatatanController::class, 'show'])->name('catatan.show');
Route::get('/catatan/{catatan}/edit', [CatatanController::class, 'edit'])->name('catatan.edit');
Route::put('/catatan/{catatan}', [CatatanController::class, 'update'])->name('catatan.update');
Route::delete('/catatan/{catatan}', [CatatanController::class, 'destroy'])->name('catatan.destroy');
});
Ada dua hal yang perlu diperhatikan di sini.
Pertama, middleware guest di route GET dan POST /login. Middleware ini adalah kebalikan dari auth — ia memblokir pengguna yang sudah login dari mengakses halaman login. Jika pengguna sudah login dan mencoba buka /login, mereka akan di-redirect ke halaman utama. Ini mencegah situasi janggal di mana pengguna yang sudah login melihat form login lagi.
Kedua, Route::middleware('auth')->group(). Semua route di dalam grup ini otomatis terlindungi. Tidak perlu menambahkan ->middleware('auth') satu per satu ke setiap route. Jika nantinya ada route catatan baru yang ditambahkan, cukup letakkan di dalam grup ini dan proteksinya langsung aktif.
Verifikasi dengan php artisan route:list:
GET|HEAD login ............. login › Auth\LoginController@showLoginForm
POST login ....................... Auth\LoginController@login
POST logout .............. logout › Auth\LoginController@logout
GET|HEAD catatan ........ catatan.index › CatatanController@index
POST catatan ........ catatan.store › CatatanController@store
...
Menambahkan Tombol Logout di Navbar
Layout resources/views/layouts/app.blade.php perlu ditambahkan tombol logout agar pengguna yang sudah login bisa keluar. Gunakan direktif @auth untuk menampilkan tombol hanya ketika pengguna sudah terautentikasi:
{{-- resources/views/layouts/app.blade.php --}}
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@yield('title', 'Manajemen Catatan')</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="bg-gray-50 min-h-screen">
<nav class="bg-white border-b border-gray-200 px-6 py-4">
<div class="max-w-4xl mx-auto flex justify-between items-center">
<a href="{{ route('catatan.index') }}" class="font-semibold text-gray-800">
Manajemen Catatan
</a>
@auth
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit" class="text-sm text-gray-600 hover:text-gray-900">
Keluar ({{ Auth::user()->name }})
</button>
</form>
@endauth
</div>
</nav>
<main class="max-w-4xl mx-auto px-6 py-8">
@if (session('success'))
<div class="mb-4 p-4 bg-green-50 border border-green-200 text-green-700 rounded">
{{ session('success') }}
</div>
@endif
@yield('content')
</main>
</body>
</html>
@auth ... @endauth adalah direktif Blade yang hanya merender konten di dalamnya jika ada pengguna yang sedang login. Auth::user()->name mengambil nama pengguna aktif dari session.
Tombol logout menggunakan form POST, bukan link GET. Ini penting: logout mengubah state aplikasi (menghapus session), sehingga harus menggunakan method POST — bukan GET yang seharusnya idempotent dan aman untuk di-cache atau di-prefetch browser.
Jangan buat route logout sebagai GET request. Browser dan proxy bisa meng-cache GET request, yang berpotensi menyebabkan pengguna logout tanpa sengaja ketika browser melakukan prefetch link.
Mencoba Proteksi
Setelah semua kode di tempat, buka / — halaman welcome bawaan Laravel. Di sana sudah tersedia link “Log in” di pojok kanan atas yang mengarah ke route login.
Klik “Log in” — browser diarahkan ke /login yang sudah kita buat.
Hal yang sama terjadi jika langsung membuka /catatan tanpa login — middleware auth memotong request dan meredirect ke halaman ini.
Untuk masuk, butuh akun di database. Jika belum ada, buat via tinker:
php artisan tinker
App\Models\User::create([
'name' => 'Admin',
'email' => 'admin@example.com',
'password' => bcrypt('password'),
]);
Coba masuk dengan kredensial yang salah terlebih dahulu — perhatikan bagaimana pesan error muncul dan field email tetap terisi karena old('email').
Setelah login dengan kredensial yang benar, akan diredirect ke /catatan. Navbar sekarang menampilkan nama pengguna aktif dan tombol “Keluar”.
Akses ke semua halaman catatan sekarang membutuhkan autentikasi yang valid. Coba buka tab baru dalam mode incognito dan akses /catatan langsung — akan diredirect ke /login.
Aplikasi Selesai
Ini adalah bab terakhir dari studi kasus. Dalam sembilan bab terakhir, kamu telah membangun aplikasi manajemen catatan dari nol — mulai dari instalasi Laravel, konfigurasi database, pembuatan model dan migrasi, hingga CRUD lengkap dengan validasi, paginasi, seeder, dan proteksi autentikasi.
Lebih dari sekadar fitur-fiturnya, perjalanan ini adalah tentang memahami bagaimana Laravel menyusun aplikasi web: lapisan routing yang bersih, controller yang fokus pada satu tanggung jawab, model yang membungkus logika database, dan middleware yang menjaga keamanan di level infrastruktur.
Aplikasi catatan ini kecil, tapi pola yang digunakan di dalamnya adalah pola yang sama yang dipakai di aplikasi Laravel skala enterprise. Satu kali kamu memahami pola ini, membangun aplikasi yang lebih kompleks hanya soal menambah model, controller, dan view baru — strukturnya tetap sama.