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.

Diagram alur middleware auth: request masuk ke middleware, jika sudah login diteruskan ke controller lalu response, jika belum login diredirect ke route login

Gambar 1: Alur kerja middleware auth

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.

Halaman welcome Laravel default dengan link Log in di pojok kanan atas

Gambar 2: Halaman welcome Laravel dengan link Log in

Klik “Log in” — browser diarahkan ke /login yang sudah kita buat.

Halaman login dengan form email, password, dan checkbox Ingat saya

Gambar 3: Halaman login aplikasi manajemen catatan

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').

Halaman login menampilkan pesan error dan email lama tetap terisi di input

Gambar 4: Pesan error login gagal dengan email tetap terisi

Setelah login dengan kredensial yang benar, akan diredirect ke /catatan. Navbar sekarang menampilkan nama pengguna aktif dan tombol “Keluar”.

Halaman listing catatan dengan nama pengguna dan tombol Keluar di navbar

Gambar 5: Listing catatan setelah login berhasil, navbar menampilkan nama user

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.

Referensi

  1. 1Authentication — Laravel 12.x Documentation
  2. 2Middleware — Laravel 12.x Documentation
  3. 3Routing: Route Groups — Laravel 12.x Documentation