Laravel 12 Livewire Login & Registration System with Roles & Permissions

Author

Kritim Yantra

Jun 03, 2025

Laravel 12 Livewire Login & Registration System with Roles & Permissions

Building a secure authentication system with role-based permissions is a fundamental requirement for most web applications. In this comprehensive guide, we'll create a modern Laravel 12 authentication system using Livewire that includes:

  • User registration and login
  • Role-based access control (Admin, User, etc.)
  • Permission management
  • Middleware protection
  • Beautiful Tailwind CSS UI

Why Livewire for Authentication?

Livewire brings modern reactive functionality to Laravel without writing JavaScript. For authentication systems, this means:

  • Real-time validation feedback
  • Dynamic UI updates
  • Simplified form handling
  • Maintainable code structure

Prerequisites

Before starting, ensure you have:

  1. Laravel 12 installed
  2. Composer installed
  3. Node.js and NPM for frontend assets
  4. Basic Laravel knowledge

Step 1: Install Required Packages

Run these commands to install necessary packages:

composer require livewire/livewire laravel/jetstream spatie/laravel-permission
npm install
npm install tailwindcss postcss autoprefixer

Step 2: Set Up Jetstream with Livewire

Configure Laravel Jetstream with Livewire:

php artisan jetstream:install livewire
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan migrate
npm run dev

Step 3: Create Roles and Permissions

Generate the migration and seed initial roles:

php artisan make:model Role -m
php artisan make:seeder RolePermissionSeeder

Update the Role migration:

// database/migrations/xxxx_xx_xx_create_roles_table.php
Schema::create('roles', function (Blueprint $table) {
    $table->id();
    $table->string('name')->unique();
    $table->string('guard_name')->default('web');
    $table->timestamps();
});

Create the seeder:

// database/seeders/RolePermissionSeeder.php
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;

public function run()
{
    // Reset cached roles and permissions
    app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();

    // Create permissions
    Permission::create(['name' => 'view dashboard']);
    Permission::create(['name' => 'manage users']);
    Permission::create(['name' => 'manage settings']);

    // Create roles and assign permissions
    $admin = Role::create(['name' => 'admin']);
    $admin->givePermissionTo(['view dashboard', 'manage users', 'manage settings']);

    $user = Role::create(['name' => 'user']);
    $user->givePermissionTo('view dashboard');
}

Run the seeder:

php artisan db:seed --class=RolePermissionSeeder

Step 4: Modify User Model

Update the User model:

// app/Models/User.php
use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, HasProfilePhoto, HasRoles;
    // ...
}

Step 5: Create Livewire Components

Generate Livewire components for registration and login:

php artisan make:livewire Auth/Register
php artisan make:livewire Auth/Login

Registration Component

// app/Http/Livewire/Auth/Register.php
namespace App\Http\Livewire\Auth;

use Livewire\Component;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Spatie\Permission\Models\Role;

class Register extends Component
{
    public $name;
    public $email;
    public $password;
    public $password_confirmation;
    public $role = 'user'; // Default role

    protected $rules = [
        'name' => 'required|min:3',
        'email' => 'required|email|unique:users',
        'password' => 'required|min:6|confirmed',
        'role' => 'required|in:user,admin',
    ];

    public function register()
    {
        $this->validate();

        $user = User::create([
            'name' => $this->name,
            'email' => $this->email,
            'password' => Hash::make($this->password),
        ]);

        $user->assignRole($this->role);

        auth()->login($user);

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

    public function render()
    {
        return view('livewire.auth.register')
            ->layout('layouts.guest');
    }
}

Login Component

// app/Http/Livewire/Auth/Login.php
namespace App\Http\Livewire\Auth;

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

class Login extends Component
{
    public $email;
    public $password;
    public $remember = false;

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

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

        if (Auth::attempt([
            'email' => $this->email,
            'password' => $this->password,
        ], $this->remember)) {
            return redirect()->intended(route('dashboard'));
        }

        $this->addError('email', 'Invalid credentials');
    }

    public function render()
    {
        return view('livewire.auth.login')
            ->layout('layouts.guest');
    }
}

Step 6: Create the Views

Registration View

<!-- resources/views/livewire/auth/register.blade.php -->
<div class="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
    <div class="max-w-md w-full space-y-8">
        <div>
            <h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900">
                Create a new account
            </h2>
        </div>
        <form class="mt-8 space-y-6" wire:submit.prevent="register">
            @csrf
            <div class="rounded-md shadow-sm space-y-4">
                <div>
                    <label for="name" class="sr-only">Name</label>
                    <input id="name" name="name" type="text" wire:model="name" required
                           class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
                           placeholder="Full Name">
                    @error('name') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
                </div>

                <div>
                    <label for="email" class="sr-only">Email address</label>
                    <input id="email" name="email" type="email" wire:model="email" required
                           class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
                           placeholder="Email address">
                    @error('email') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
                </div>

                <div>
                    <label for="password" class="sr-only">Password</label>
                    <input id="password" name="password" type="password" wire:model="password" required
                           class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
                           placeholder="Password">
                    @error('password') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
                </div>

                <div>
                    <label for="password_confirmation" class="sr-only">Confirm Password</label>
                    <input id="password_confirmation" name="password_confirmation" type="password" wire:model="password_confirmation" required
                           class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
                           placeholder="Confirm Password">
                </div>

                <div>
                    <label for="role" class="block text-sm font-medium text-gray-700">Account Type</label>
                    <select id="role" wire:model="role" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md">
                        <option value="user">Standard User</option>
                        <option value="admin">Administrator</option>
                    </select>
                </div>
            </div>

            <div>
                <button type="submit"
                        class="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
                    Register
                </button>
            </div>
        </form>
    </div>
</div>

Login View

<!-- resources/views/livewire/auth/login.blade.php -->
<div class="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
    <div class="max-w-md w-full space-y-8">
        <div>
            <h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900">
                Sign in to your account
            </h2>
        </div>
        <form class="mt-8 space-y-6" wire:submit.prevent="login">
            @csrf
            <div class="rounded-md shadow-sm space-y-4">
                <div>
                    <label for="email" class="sr-only">Email address</label>
                    <input id="email" name="email" type="email" wire:model="email" required
                           class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
                           placeholder="Email address">
                    @error('email') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
                </div>

                <div>
                    <label for="password" class="sr-only">Password</label>
                    <input id="password" name="password" type="password" wire:model="password" required
                           class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
                           placeholder="Password">
                    @error('password') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
                </div>
            </div>

            <div class="flex items-center justify-between">
                <div class="flex items-center">
                    <input id="remember" name="remember" type="checkbox" wire:model="remember"
                           class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">
                    <label for="remember" class="ml-2 block text-sm text-gray-900">
                        Remember me
                    </label>
                </div>

                <div class="text-sm">
                    <a href="{{ route('password.request') }}" class="font-medium text-indigo-600 hover:text-indigo-500">
                        Forgot your password?
                    </a>
                </div>
            </div>

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

Step 7: Create Routes

Update your web.php routes:

use App\Http\Livewire\Auth\Login;
use App\Http\Livewire\Auth\Register;
use Illuminate\Support\Facades\Route;

Route::middleware('guest')->group(function () {
    Route::get('/login', Login::class)->name('login');
    Route::get('/register', Register::class)->name('register');
});

Route::middleware(['auth', 'verified'])->group(function () {
    Route::get('/dashboard', function () {
        return view('dashboard');
    })->name('dashboard')->middleware('permission:view dashboard');
    
    // Admin routes
    Route::middleware('role:admin')->group(function () {
        Route::get('/admin/users', function () {
            return view('admin.users');
        })->name('admin.users')->middleware('permission:manage users');
    });
});

Step 8: Create Middleware for Permissions

Create a middleware to check permissions:

php artisan make:middleware CheckPermission

Update the middleware:

// app/Http/Middleware/CheckPermission.php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class CheckPermission
{
    public function handle(Request $request, Closure $next, $permission)
    {
        if (!Auth::check() || !Auth::user()->can($permission)) {
            abort(403, 'Unauthorized action.');
        }

        return $next($request);
    }
}

Register the middleware in bootstrap/app.php:


->withMiddleware(function ($middleware) {
    $middleware->alias([
       'permission' => \App\Http\Middleware\CheckPermission::class,
    ]);
})

Step 9: Create the Dashboard View

Create a basic dashboard that shows different content based on roles:

<!-- resources/views/dashboard.blade.php -->
@extends('layouts.app')

@section('content')
<div class="py-12">
    <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
        <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
            <div class="p-6 bg-white border-b border-gray-200">
                <h1 class="text-2xl font-bold mb-4">Dashboard</h1>
                
                @role('admin')
                    <div class="bg-blue-100 border-l-4 border-blue-500 text-blue-700 p-4 mb-4">
                        <p>Welcome Admin! You have full access to the system.</p>
                    </div>
                @endrole

                @role('user')
                    <div class="bg-green-100 border-l-4 border-green-500 text-green-700 p-4 mb-4">
                        <p>Welcome User! You have standard access.</p>
                    </div>
                @endrole

                <div class="mt-6">
                    <h2 class="text-xl font-semibold">Your Permissions:</h2>
                    <ul class="list-disc pl-5 mt-2">
                        @foreach(Auth::user()->getAllPermissions() as $permission)
                            <li>{{ $permission->name }}</li>
                        @endforeach
                    </ul>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Step 10: Test Your Authentication System

  1. Register as a regular user and verify permissions
  2. Register as an admin (you might want to protect this route in production)
  3. Test login functionality
  4. Verify role-based access control

Advanced Features to Implement

  1. Email Verification: Add Jetstream's email verification
  2. Two-Factor Authentication: Enhance security with 2FA
  3. Profile Management: Allow users to update their profiles
  4. Password Reset: Implement password recovery
  5. Activity Logging: Track user logins and important actions

Security Best Practices

  1. Always use HTTPS
  2. Implement rate limiting for login attempts
  3. Use strong password policies
  4. Regularly update dependencies
  5. Sanitize all user inputs

Conclusion

You've now built a complete Laravel 12 authentication system with Livewire that includes:

  • Modern, reactive forms
  • Role-based access control
  • Permission management
  • Beautiful Tailwind CSS interface
  • Secure middleware protection

This system provides a solid foundation for any application requiring user authentication with different access levels. Remember to customize it further based on your specific requirements and always prioritize security when handling user authentication.

For production applications, consider adding additional security measures like two-factor authentication and activity monitoring.

Happy coding! 🚀

Tags

Comments

No comments yet. Be the first to comment!

Please log in to post a comment:

Sign in with Google

Related Posts