Skip to main content
Back to Blog

Laravel 12 Authentication with Breeze and Livewire 4 (2025 Guide)

Laravel 12 Authentication with Breeze and Livewire 4 (2025 Guide) – cover image

Updated for Laravel 12, Livewire 4, and PHP 8.2–8.4

If you build Laravel apps for real clients, authentication is one of the first features you ship. In Laravel 12, we still have the lightweight Laravel Breeze starter, and with Livewire 4 we can turn the login experience into a smooth, reactive form without touching a single line of JavaScript.

In this guide you’ll set up a fresh Laravel 12 project, install Breeze, add Livewire 4, and rebuild the login form as a Livewire component with real-time validation and a clean UX.

1. Prerequisites

You’ll need the following to follow along:

  • PHP 8.2 or higher (Laravel 12 supports PHP 8.2–8.4)
  • Composer installed globally
  • Node.js and npm (for building frontend assets)
  • A database (MySQL, PostgreSQL, SQLite, etc.)
php -v

If PHP is below 8.2, upgrade before continuing.

2. Create a New Laravel 12 Project

laravel new breeze-livewire-auth

When the installer asks for a starter kit, choose None. We’ll install Breeze manually, then enhance it with Livewire.

cd breeze-livewire-auth

php artisan migrate

3. Install Laravel Breeze on Laravel 12

Breeze is Laravel’s minimal authentication starter kit. It scaffolds login, registration, password reset, email verification and a simple Tailwind-based UI.

composer require laravel/breeze --dev

php artisan breeze:install blade

Now install and build the frontend assets:

npm install
npm run build    # or: npm run dev

Start the server with php artisan serve and visit /login to confirm that Breeze authentication works.

4. Install Livewire 4

Next, pull in Livewire 4 so we can make the login experience reactive.

composer require livewire/livewire

Add Livewire styles and scripts to Breeze’s app layout:

<!-- resources/views/layouts/app.blade.php -->
<head>
    ...
    @livewireStyles
</head>

<body>
    ...
    @livewireScripts
</body>

5. Build a Livewire Login Component

Instead of posting the Breeze login form to a controller, we’ll move the logic into a Livewire component for a smoother experience.

php artisan make:livewire Auth.LoginForm

5.1 Component class

<?php

namespace App\Livewire\Auth;

use Illuminate\Support\Facades\Auth;
use Livewire\Component;

class LoginForm extends Component
{
    public string $email = '';
    public string $password = '';
    public bool $remember = false;

    protected array $rules = [
        'email'    => ['required', 'email'],
        'password' => ['required'],
    ];

    public function updated($property)
    {
        $this->validateOnly($property);
    }

    public function login()
    {
        $credentials = $this->validate();

        if (! Auth::attempt([
            'email'    => $this->email,
            'password' => $this->password,
        ], $this->remember)) {
            $this->addError('email', 'The provided credentials do not match our records.');
            return;
        }

        session()->regenerate();

        return redirect()->intended(route('dashboard'));
    }

    public function render()
    {
        return view('livewire.auth.login-form');
    }
}

5.2 Blade view

<!-- resources/views/livewire/auth/login-form.blade.php -->
<div class="w-full max-w-sm mx-auto space-y-4">
    <header class="space-y-1">
        <h1 class="text-xl font-semibold text-gray-900">Log in</h1>
        <p class="text-xs text-gray-500">
            Use your account to access the dashboard.
        </p>
    </header>

    <form wire:submit.prevent="login" class="space-y-4">
        <!-- Email -->
        <div class="space-y-1">
            <label for="email" class="block text-xs font-medium text-gray-700">
                Email
            </label>
            <input
                id="email"
                type="email"
                wire:model.defer="email"
                autocomplete="email"
                class="block w-full rounded-md border-gray-300 text-sm shadow-sm
                       focus:border-indigo-500 focus:ring-indigo-500"
            >
            @error('email')
                <p class="text-xs text-red-600 mt-1">{{ $message }}</p>
            @enderror
        </div>

        <!-- Password -->
        <div class="space-y-1">
            <label for="password" class="block text-xs font-medium text-gray-700">
                Password
            </label>
            <input
                id="password"
                type="password"
                wire:model.defer="password"
                autocomplete="current-password"
                class="block w-full rounded-md border-gray-300 text-sm shadow-sm
                       focus:border-indigo-500 focus:ring-indigo-500"
            >
            @error('password')
                <p class="text-xs text-red-600 mt-1">{{ $message }}</p>
            @enderror
        </div>

        <!-- Remember + Forgot password -->
        <div class="flex flex-col xs:flex-row xs:items-center xs:justify-between gap-2">
            <label class="inline-flex items-center space-x-2 text-xs text-gray-700">
                <input
                    type="checkbox"
                    wire:model="remember"
                    class="rounded border-gray-300 text-indigo-600 shadow-sm
                           focus:ring-indigo-500"
                >
                <span>Remember me</span>
            </label>

            @if (Route::has('password.request'))
                <a href="{{ route('password.request') }}"
                   class="text-xs text-indigo-600 hover:text-indigo-500">
                    Forgot password?
                </a>
            @endif
        </div>

        <button
            type="submit"
            class="inline-flex w-full items-center justify-center rounded-md
                   bg-indigo-600 px-3 py-2 text-sm font-medium text-white
                   hover:bg-indigo-700 focus:outline-none focus:ring-2
                   focus:ring-indigo-500 focus:ring-offset-2"
        >
            Log in
        </button>
    </form>
</div>

Now update Breeze’s login view to use the component:

<!-- resources/views/auth/login.blade.php -->
<x-guest-layout>
    <div class="mt-6">
        <livewire:auth.login-form />
    </div>
</x-guest-layout>

6. Security and Best Practices

  • Always serve production apps over HTTPS.
  • Keep Laravel, Breeze, and Livewire updated.
  • Use Laravel’s built-in rate limiting for login attempts.
  • Enable email verification for user-facing apps.
  • Consider 2FA or social login for larger or high-risk projects.

7. Conclusion

Breeze gives you a clean, minimal starting point for authentication in Laravel 12, while Livewire 4 brings it to life with a reactive, modern UX. Together they form a powerful stack that lets you move quickly, especially when you work solo or in a small team.

From here you can extend the setup with registration, email verification, password reset flows, or even two-factor authentication — all while keeping your stack simple and Laravel-first.

Need a custom Laravel authentication system or dashboard for your business? Feel free to reach out — this is exactly what I help clients build.