Kritim Yantra
Jun 03, 2025
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:
Livewire brings modern reactive functionality to Laravel without writing JavaScript. For authentication systems, this means:
Before starting, ensure you have:
Run these commands to install necessary packages:
composer require livewire/livewire laravel/jetstream spatie/laravel-permission
npm install
npm install tailwindcss postcss autoprefixer
Configure Laravel Jetstream with Livewire:
php artisan jetstream:install livewire
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan migrate
npm run dev
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
Update the User
model:
// app/Models/User.php
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, HasProfilePhoto, HasRoles;
// ...
}
Generate Livewire components for registration and login:
php artisan make:livewire Auth/Register
php artisan make:livewire Auth/Login
// 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');
}
}
// 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');
}
}
<!-- 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>
<!-- 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>
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');
});
});
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,
]);
})
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
You've now built a complete Laravel 12 authentication system with Livewire that includes:
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! 🚀
No comments yet. Be the first to comment!
Please log in to post a comment:
Sign in with Google