BAB 13: Authentication

Mengimplementasikan sistem login, register, dan manajemen sesi pengguna di Laravel.

Aplikasi catatan yang kita bangun sejak bab-bab sebelumnya sudah bisa menerima input, memvalidasinya, dan menyimpannya. Tapi semua itu masih “terbuka”: siapapun yang tahu URL-nya bisa mengakses halaman manapun. Tidak ada konsep pengguna, tidak ada kepemilikan data, tidak ada batasan akses.

Autentikasi mengubah semua itu. Dengan autentikasi, aplikasi tahu siapa yang sedang berinteraksi dengannya — dan dari situlah semua keputusan akses dimulai.

Cara Kerja Auth di Laravel

Laravel mengelola autentikasi melalui dua konsep utama: guard dan provider. Guard menentukan bagaimana pengguna diautentikasi per request — apakah lewat session (untuk browser) atau token (untuk API). Provider menentukan darimana data pengguna diambil — dalam kasus kita, dari database via Eloquent.

Konfigurasi defaultnya ada di config/auth.php:

// config/auth.php
'defaults' => [
    'guard' => 'web',
    'passwords' => 'users',
],

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
],

'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\Models\User::class,
    ],
],

Guard web menggunakan session — setelah login berhasil, Laravel menyimpan ID pengguna di session dan membacanya di setiap request berikutnya. Ini adalah mekanisme yang kita gunakan untuk aplikasi berbasis browser.

Diagram alur autentikasi berbasis session antara Browser dan Laravel Server

Gambar 1: Alur session-based authentication di Laravel

Alur di atas terjadi secara otomatis setiap kali kita menggunakan Auth::attempt() dan middleware auth.

Tabel Users

Laravel sudah menyertakan migration untuk tabel users sejak instalasi awal. Jalankan php artisan migrate jika belum pernah, dan tabel itu akan terbentuk otomatis dengan kolom id, name, email, password, dan remember_token.

Model User juga sudah tersedia di app/Models/User.php. Yang penting diperhatikan adalah dua interface yang diimplementasikannya:

// app/Models/User.php (sudah ada, tidak perlu dibuat)
class User extends Authenticatable
{
    use HasFactory, Notifiable;

    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    protected $hidden = [
        'password',
        'remember_token',
    ];

    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed',
        ];
    }
}

Cast 'password' => 'hashed' adalah fitur Laravel 12 yang otomatis meng-hash password saat disimpan via mass assignment — kita tidak perlu memanggil bcrypt() atau Hash::make() secara manual ketika menggunakan $fillable.

Membuat Form Register

Kita akan membuat authentication secara manual agar kamu memahami cara kerjanya dari dalam. Mulai dari register — proses membuat akun baru.

Daftarkan rute-rute yang diperlukan:

// routes/web.php
use App\Http\Controllers\Auth\RegisterController;
use App\Http\Controllers\Auth\LoginController;

// Register
Route::get('/register', [RegisterController::class, 'tampilkan'])->name('register');
Route::post('/register', [RegisterController::class, 'simpan']);

// Login
Route::get('/login', [LoginController::class, 'tampilkan'])->name('login');
Route::post('/login', [LoginController::class, 'masuk']);
Route::post('/logout', [LoginController::class, 'keluar'])->name('logout');

Buat controller untuk register:

<?php
// app/Http/Controllers/Auth/RegisterController.php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;

class RegisterController extends Controller
{
    public function tampilkan(): View
    {
        return view('auth.register');
    }

    public function simpan(Request $request): RedirectResponse
    {
        $data = $request->validate([
            'name'                  => 'required|string|max:255',
            'email'                 => 'required|email|unique:users,email',
            'password'              => 'required|string|min:8|confirmed',
        ]);

        User::create($data);

        return redirect()->route('login')
            ->with('sukses', 'Akun berhasil dibuat. Silakan masuk.');
    }
}

Rule confirmed memastikan ada field password_confirmation yang nilainya sama dengan password. Rule unique:users,email memastikan email belum digunakan akun lain.

Buat view-nya:

{{-- resources/views/auth/register.blade.php --}}
<x-app-layout>
    <div class="max-w-md mx-auto mt-8 p-6 bg-white rounded-lg shadow">
        <h1 class="text-2xl font-bold mb-6">Buat Akun Baru</h1>

        @if (session('sukses'))
            <div class="mb-4 p-3 bg-green-100 text-green-700 rounded">
                {{ session('sukses') }}
            </div>
        @endif

        <form method="POST" action="/register" class="space-y-4">
            @csrf

            <div>
                <label for="name" class="block text-sm font-medium mb-1">Nama</label>
                <input type="text" id="name" name="name"
                    value="{{ old('name') }}"
                    class="w-full border rounded px-3 py-2 @error('name') border-red-500 @enderror">
                @error('name')
                    <p class="text-red-500 text-sm mt-1">{{ $message }}</p>
                @enderror
            </div>

            <div>
                <label for="email" class="block text-sm font-medium mb-1">Email</label>
                <input type="email" id="email" name="email"
                    value="{{ old('email') }}"
                    class="w-full border rounded px-3 py-2 @error('email') border-red-500 @enderror">
                @error('email')
                    <p class="text-red-500 text-sm mt-1">{{ $message }}</p>
                @enderror
            </div>

            <div>
                <label for="password" class="block text-sm font-medium mb-1">Password</label>
                <input type="password" id="password" name="password"
                    class="w-full border rounded px-3 py-2 @error('password') border-red-500 @enderror">
                @error('password')
                    <p class="text-red-500 text-sm mt-1">{{ $message }}</p>
                @enderror
            </div>

            <div>
                <label for="password_confirmation" class="block text-sm font-medium mb-1">
                    Konfirmasi Password
                </label>
                <input type="password" id="password_confirmation" name="password_confirmation"
                    class="w-full border rounded px-3 py-2">
            </div>

            <button type="submit"
                class="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700">
                Daftar
            </button>
        </form>

        <p class="text-center mt-4 text-sm">
            Sudah punya akun? <a href="{{ route('login') }}" class="text-blue-600">Masuk</a>
        </p>
    </div>
</x-app-layout>

Membuat Form Login

Login memerlukan langkah lebih — kita perlu memverifikasi kredensial, dan jika berhasil, membuat session pengguna.

<?php
// app/Http/Controllers/Auth/LoginController.php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;

class LoginController extends Controller
{
    public function tampilkan(): View
    {
        return view('auth.login');
    }

    public function masuk(Request $request): RedirectResponse
    {
        $kredensial = $request->validate([
            'email'    => 'required|email',
            'password' => 'required|string',
        ]);

        if (Auth::attempt($kredensial, $request->boolean('ingat_saya'))) {
            $request->session()->regenerate();

            return redirect()->intended(route('catatan.index'));
        }

        return back()->withErrors([
            'email' => 'Email atau password tidak cocok.',
        ])->onlyInput('email');
    }

    public function keluar(Request $request): RedirectResponse
    {
        Auth::logout();

        $request->session()->invalidate();
        $request->session()->regenerateToken();

        return redirect()->route('login');
    }
}

Ada tiga detail penting di method masuk():

Auth::attempt() menerima kredensial dan secara otomatis membandingkan password yang dikirim dengan hash di database — kita tidak perlu memverifikasi password secara manual. Parameter kedua adalah nilai boolean untuk fitur “ingat saya”.

$request->session()->regenerate() dipanggil setelah login berhasil untuk mencegah session fixation attack — serangan di mana penyerang menggunakan session ID yang sama sebelum dan sesudah login.

redirect()->intended() mengarahkan pengguna ke halaman yang awalnya ingin mereka buka sebelum diarahkan ke login. Jika tidak ada halaman sebelumnya, ia menggunakan fallback yang kita tentukan.

Jangan pernah melewatkan $request->session()->regenerate() setelah login berhasil. Ini adalah langkah keamanan kritis, bukan opsional.

Buat view login-nya:

{{-- resources/views/auth/login.blade.php --}}
<x-app-layout>
    <div class="max-w-md mx-auto mt-8 p-6 bg-white rounded-lg shadow">
        <h1 class="text-2xl font-bold mb-6">Masuk</h1>

        @if (session('sukses'))
            <div class="mb-4 p-3 bg-green-100 text-green-700 rounded">
                {{ session('sukses') }}
            </div>
        @endif

        <form method="POST" action="{{ route('login') }}" class="space-y-4">
            @csrf

            <div>
                <label for="email" class="block text-sm font-medium mb-1">Email</label>
                <input type="email" id="email" name="email"
                    value="{{ old('email') }}"
                    class="w-full border rounded px-3 py-2 @error('email') border-red-500 @enderror"
                    autofocus>
                @error('email')
                    <p class="text-red-500 text-sm mt-1">{{ $message }}</p>
                @enderror
            </div>

            <div>
                <label for="password" class="block text-sm font-medium mb-1">Password</label>
                <input type="password" id="password" name="password"
                    class="w-full border rounded px-3 py-2 @error('password') border-red-500 @enderror">
                @error('password')
                    <p class="text-red-500 text-sm mt-1">{{ $message }}</p>
                @enderror
            </div>

            <div class="flex items-center">
                <input type="checkbox" id="ingat_saya" name="ingat_saya"
                    class="mr-2">
                <label for="ingat_saya" class="text-sm">Ingat saya</label>
            </div>

            <button type="submit"
                class="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700">
                Masuk
            </button>
        </form>

        <p class="text-center mt-4 text-sm">
            Belum punya akun? <a href="{{ route('register') }}" class="text-blue-600">Daftar</a>
        </p>
    </div>
</x-app-layout>

Mengakses Pengguna yang Login

Setelah pengguna berhasil masuk, ada beberapa cara untuk mengakses data mereka di seluruh aplikasi.

Lewat Auth facade di controller:

use Illuminate\Support\Facades\Auth;

// Mendapatkan instance User yang sedang login
$pengguna = Auth::user();

// Hanya mengambil ID-nya
$idPengguna = Auth::id();

// Memeriksa apakah ada pengguna yang login
if (Auth::check()) {
    // ada pengguna yang terautentikasi
}

Lewat $request->user() — cara yang lebih eksplisit dan mudah di-test:

public function index(Request $request)
{
    $pengguna = $request->user();
    $catatan = $pengguna->catatan()->latest()->get();

    return view('catatan.index', compact('catatan'));
}

Di Blade template, Auth facade juga tersedia lewat directive @auth dan @guest:

{{-- resources/views/layouts/app.blade.php --}}
<nav>
    @auth
        <span>Halo, {{ Auth::user()->name }}</span>
        <form method="POST" action="{{ route('logout') }}">
            @csrf
            <button type="submit">Keluar</button>
        </form>
    @endauth

    @guest
        <a href="{{ route('login') }}">Masuk</a>
        <a href="{{ route('register') }}">Daftar</a>
    @endguest
</nav>

@auth merender konten hanya jika ada pengguna yang login, @guest untuk kondisi sebaliknya.

Melindungi Rute dengan Middleware Auth

Middleware auth adalah cara kita mencegah pengguna yang belum login mengakses halaman tertentu. Tambahkan ke rute yang perlu dilindungi:

// routes/web.php
Route::middleware('auth')->group(function () {
    Route::resource('catatan', CatatanController::class);
});

Semua rute CRUD catatan sekarang hanya bisa diakses pengguna yang sudah login. Jika pengguna yang belum login mencoba mengaksesnya, Laravel otomatis mengarahkan mereka ke rute bernama login.

redirect()->intended() di controller login bekerja bersamaan dengan middleware ini — Laravel menyimpan URL tujuan awal pengguna, lalu mengarahkan mereka ke sana setelah berhasil login.

Latihan

Coba kerjakan pengembangan berikut pada aplikasi catatan:

  1. Kepemilikan catatan — Tambahkan kolom user_id ke tabel catatan (via migrasi baru). Modifikasi CatatanController@store agar secara otomatis mengisi user_id dengan Auth::id(). Pastikan index hanya menampilkan catatan milik pengguna yang sedang login.

  2. Rate limiting login — Tambahkan throttle 6,1 ke rute POST login untuk membatasi percobaan login. Tampilkan pesan error yang informatif ketika limit tercapai menggunakan $exception->getMessage() dari ValidationException.

  3. Redirect setelah logout — Saat ini logout mengarahkan ke halaman login. Ubah agar pengarahan ke halaman beranda dengan flash message “Kamu telah berhasil keluar.”

Penutup Bab

Autentikasi memberi identitas pada setiap interaksi di aplikasi kita. Dengan Auth::attempt(), session()->regenerate(), dan middleware auth, kita sudah punya sistem login yang fungsional dan aman secara fundamental.

Tapi mengetahui siapa yang login baru setengah dari cerita. Pertanyaan berikutnya adalah: pengguna mana yang boleh melakukan apa? Apakah semua pengguna yang login bisa menghapus catatan siapapun, atau hanya catatan milik mereka sendiri? Itulah domain authorization — dan itu yang akan kita pelajari di bab berikutnya.

Referensi

  1. 1Authentication — Laravel 12.x Documentation
  2. 2Protecting Routes — Laravel 12.x Middleware Documentation