Authentication di Laravel β€” Breeze, Fortify, dan Alur Lengkapnya

Kalau kamu baru mulai belajar authentication di Laravel, kamu pasti pernah bingung melihat daftar namanya: Breeze, Jetstream, Fortify, Sanctum, Passport. Semuanya berkaitan dengan auth, tapi tidak jelas mana yang harus dipakai dan apa bedanya.

Sebelum masuk ke kode, kita perlu ngerti dulu siapa yang melakukan apa β€” karena di sinilah kebingungan itu biasanya berasal.


πŸ—ΊοΈ Peta Ekosistem Auth Laravel

Bayangkan auth di Laravel seperti restoran. Ada tiga lapisan yang berbeda tugasnya:

Breeze / Jetstream
  ↓
"Ruang makan + dekorasi" β€” UI yang dilihat user (form login, register, halaman profil)

Fortify
  ↓
"Dapur" β€” engine auth yang memproses semua logika (validasi, login, reset password)

Sanctum / Passport
  ↓
"Sistem kasir khusus" β€” manajemen token untuk API dan SPA

Breeze adalah starter kit yang paling simpel β€” dia install Fortify di balik layar dan sekaligus menyediakan halaman-halaman Blade (atau Inertia/Vue) yang sudah jadi.

Fortify adalah headless authentication β€” dia hanya menyediakan logika dan route, tanpa UI sama sekali. Cocok kalau kamu mau bikin tampilan sendiri atau butuh auth untuk SPA.

Sanctum dipakai untuk menerbitkan token API β€” baik personal access token untuk mobile app maupun cookie-based auth untuk SPA.


πŸ”‘ Bagian 1 β€” Alur Authentication Breeze (Web / Session)

Install

composer require laravel/breeze --dev
php artisan breeze:install blade   # versi Blade biasa
# atau
php artisan breeze:install vue     # versi Inertia + Vue

npm install && npm run dev
php artisan migrate

Setelah ini, Laravel menyediakan route, controller, dan view untuk: register, login, logout, forgot password, reset password, dan verifikasi email.


Alur Register

Ini yang terjadi ketika pengguna baru mengisi form registrasi:

1. User isi form β†’ POST /register

2. RegisteredUserController@store dipanggil

3. Validasi input (nama, email, password)
   └── Email harus unik, password minimal 8 karakter

4. User::create([...]) β€” password otomatis di-hash lewat bcrypt

5. Auth::login($user) β€” user langsung di-login setelah register

6. Session dibuat, session ID disimpan di cookie browser

7. Redirect ke /dashboard

Di balik layar, proses ini ditangani Fortify β€” Breeze hanya menyediakan tampilan form-nya.


Alur Login

1. User isi email + password β†’ POST /login

2. Fortify::authenticateUsing dipanggil
   └── Default: cek email + password dengan Auth::attempt()

3. Auth::attempt(['email' => ..., 'password' => ...])
   └── Ambil user dari database berdasarkan email
   └── Verifikasi password dengan Hash::check()

4. Kalau cocok β†’ Auth::login($user)
   └── Session dibuat
   └── Session ID dikirim ke browser via cookie

5. Redirect ke /dashboard (atau intended URL)
   Kalau gagal β†’ redirect balik ke form login dengan error

Session β€” Apa yang Sebenarnya Terjadi

Ini bagian yang sering tidak dipahami secara utuh.

Browser                          Server (Laravel)
  β”‚                                    β”‚
  β”‚  POST /login (email + password)    β”‚
  β”‚ ─────────────────────────────────► β”‚
  β”‚                                    β”‚  Validasi credentials
  β”‚                                    β”‚  Buat session
  β”‚                                    β”‚  Simpan user_id di session storage
  β”‚                                    β”‚
  β”‚  Set-Cookie: laravel_session=xyz   β”‚
  β”‚ ◄───────────────────────────────── β”‚
  β”‚                                    β”‚
  β”‚  GET /dashboard                    β”‚
  β”‚  Cookie: laravel_session=xyz       β”‚
  β”‚ ─────────────────────────────────► β”‚
  β”‚                                    β”‚  Baca cookie β†’ cari session
  β”‚                                    β”‚  Session ada β†’ ambil user_id
  β”‚                                    β”‚  Auth::user() tersedia
  β”‚                                    β”‚
  β”‚  Response: halaman dashboard       β”‚
  β”‚ ◄───────────────────────────────── β”‚

Yang disimpan di cookie browser bukan data user β€” hanya ID session. Data aslinya (termasuk user_id) tersimpan di server (file, database, atau Redis tergantung konfigurasi SESSION_DRIVER).


Middleware auth β€” Penjaga Route

Setelah user login, route yang butuh autentikasi dilindungi dengan middleware auth:

// routes/web.php
Route::middleware('auth')->group(function () {
    Route::get('/dashboard', DashboardController::class);
    Route::resource('/posts', PostController::class);
});

Yang terjadi ketika request masuk ke route ini:

Request β†’ Middleware auth dipanggil
             β”‚
             β”œβ”€β”€ Auth::check() β†’ apakah session punya user yang valid?
             β”‚
             β”œβ”€β”€ Ya β†’ lanjutkan ke controller
             β”‚
             └── Tidak β†’ redirect ke /login

Mengakses user yang sedang login di controller:

// Cara 1 β€” lewat facade
$user = Auth::user();
$id   = Auth::id();

// Cara 2 β€” lewat request (lebih disukai di controller)
$user = $request->user();

// Cara 3 β€” helper global
$user = auth()->user();

Alur Logout

// AuthenticatedSessionController@destroy

public function destroy(Request $request): RedirectResponse
{
    Auth::guard('web')->logout();

    // Hapus data session saat ini
    $request->session()->invalidate();

    // Regenerate CSRF token untuk keamanan
    $request->session()->regenerateToken();

    return redirect('/');
}

Kustomisasi Logika Login

Fortify memungkinkan kamu mengganti logika autentikasi sepenuhnya lewat Fortify::authenticateUsing():

// app/Providers/FortifyServiceProvider.php

use Laravel\Fortify\Fortify;
use Illuminate\Support\Facades\Hash;

public function boot(): void
{
    // Ganti logika default dengan logika kustom
    Fortify::authenticateUsing(function (Request $request) {
        $user = User::where('email', $request->email)->first();

        if ($user && Hash::check($request->password, $user->password)) {
            // Tambahkan kondisi ekstra β€” misalnya cek status akun
            if (! $user->is_active) {
                return null; // tolak login
            }

            return $user;
        }

        return null; // credentials salah
    });
}

πŸ“‘ Bagian 2 β€” Authentication untuk API (Token-based)

Untuk API yang dikonsumsi mobile app atau SPA, session tidak relevan β€” karena HTTP stateless dan client tidak bisa menyimpan cookie dengan cara yang sama.

Solusinya adalah token: server menerbitkan token saat login, client menyimpannya, dan menyertakannya di setiap request berikutnya di header Authorization.

Install Sanctum

composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate

Tambahkan trait HasApiTokens ke model User:

use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;
}

Alur Login API

// routes/api.php
Route::post('/login', [AuthController::class, 'login']);
Route::middleware('auth:sanctum')->group(function () {
    Route::get('/me', [AuthController::class, 'me']);
    Route::post('/logout', [AuthController::class, 'logout']);
});
// app/Http/Controllers/AuthController.php

class AuthController extends Controller
{
    public function login(Request $request)
    {
        $request->validate([
            'email'    => 'required|email',
            'password' => 'required',
        ]);

        // Cek credentials
        if (! Auth::attempt($request->only('email', 'password'))) {
            return response()->json([
                'message' => 'Email atau password salah'
            ], 401);
        }

        $user  = Auth::user();

        // Buat token β€” beri nama sesuai device/keperluan
        $token = $user->createToken('mobile-app')->plainTextToken;

        return response()->json([
            'token' => $token,
            'user'  => $user,
        ]);
    }

    public function me(Request $request)
    {
        // Auth::user() tersedia karena middleware auth:sanctum
        return response()->json($request->user());
    }

    public function logout(Request $request)
    {
        // Hapus hanya token yang sedang dipakai
        $request->user()->currentAccessToken()->delete();

        return response()->json(['message' => 'Logged out']);
    }
}

Alur Request API dengan Token

Mobile App                       Laravel API
    β”‚                                 β”‚
    β”‚  POST /api/login                β”‚
    β”‚  {email, password}              β”‚
    β”‚ ──────────────────────────────► β”‚
    β”‚                                 β”‚  Validasi β†’ buat token
    β”‚  {token: "1|abc123..."}         β”‚
    β”‚ ◄────────────────────────────── β”‚
    β”‚                                 β”‚
    β”‚  Simpan token di storage lokal  β”‚
    β”‚                                 β”‚
    β”‚  GET /api/me                    β”‚
    β”‚  Authorization: Bearer 1|abc123 β”‚
    β”‚ ──────────────────────────────► β”‚
    β”‚                                 β”‚  Middleware auth:sanctum
    β”‚                                 β”‚  Cari token di tabel personal_access_tokens
    β”‚                                 β”‚  Token valid β†’ ambil user
    β”‚                                 β”‚
    β”‚  {id: 1, name: "Budi", ...}     β”‚
    β”‚ ◄────────────────────────────── β”‚

πŸ”„ Bagian 3 β€” Session Auth vs Token Auth

Ini perbandingan yang perlu dipahami sebelum memilih pendekatan:

Session Auth (Breeze / web)          Token Auth (Sanctum / API)
────────────────────────────         ──────────────────────────
State tersimpan di server            Stateless β€” server tidak
(file / database / redis)            simpan state apapun

Identifikasi via cookie              Identifikasi via header
laravel_session=xyz                  Authorization: Bearer xyz

CSRF protection dibutuhkan           CSRF tidak relevan
(karena cookie otomatis terkirim)    (token harus eksplisit dikirim)

Cocok untuk:                         Cocok untuk:
  - Web app tradisional              - REST API
  - Server-side rendered (Blade)     - Mobile app
  - SPA same-domain                  - SPA cross-domain

πŸ—ΊοΈ Gambaran Besar β€” Semua Komponen dan Posisinya

Permintaan login dari user
         β”‚
         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         routes/web.php          β”‚
β”‚   POST /login                   β”‚
β”‚   Middleware: guest             β”‚ ← hanya boleh diakses kalau belum login
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚
               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Fortify (engine di balik layar)β”‚
β”‚  authenticateUsing()            β”‚
β”‚   β†’ Auth::attempt()             β”‚
β”‚   β†’ Hash::check()               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚
       β”Œβ”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”
       β”‚ Berhasil       β”‚ Gagal
       β–Ό               β–Ό
Auth::login()    redirect + error
Session dibuat
       β”‚
       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Route berikutnya               β”‚
β”‚  Middleware: auth               β”‚ ← cek session / token
β”‚  Auth::user() tersedia          β”‚
β”‚  Controller berjalan            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“Š Perbandingan Breeze vs Fortify vs Sanctum

BreezeFortifySanctum
Menyediakan UIβœ… Blade / Inertia❌❌
Logika authLewat Fortifyβœ… Langsungβ€”
Untuk web (session)βœ…βœ…βœ… (SPA)
Untuk API (token)βŒβŒβœ…
Install otomatisβœ…ManualManual
Cocok untukStarter cepatKustomisasi penuhAPI / Mobile / SPA

πŸ’‘ Ingat!

Dua hal yang paling sering bikin bingung di auth Laravel:

Pertama β€” Breeze bukan pengganti Fortify. Breeze menggunakan Fortify. Kalau kamu install Breeze, Fortify sudah ada di dalamnya. Kalau kamu mau kustomisasi logika login, kamu edit di Fortify β€” bukan di Breeze.

Kedua β€” middleware auth dan auth:sanctum itu berbeda. auth (tanpa guard) menggunakan session β€” cocok untuk route web. auth:sanctum membaca token dari header Authorization β€” cocok untuk route API. Salah pilih guard, request yang valid pun bisa ditolak.

Dan satu kebiasaan yang sangat direkomendasikan: selalu pisahkan route web di routes/web.php dan route API di routes/api.php. Keduanya punya middleware group yang berbeda β€” web group punya session dan CSRF, api group tidak.