Laravel 12 Multi-User-Rollenverwaltung – Implementierungsdokumentation
Diese Dokumentation beschreibt die Implementierung eines Multi-User-Rollenverwaltungssystems für eine Laravel 12-Anwendung mit Livewire. Alle Implementierungsschritte sind chronologisch dokumentiert.
1. Konzeption des Rollensystems
Für diese Anwendung wurden vier Rollen definiert:
- Admin: Vollständiger Zugriff auf alle Funktionen, inkl. Rollenverwaltung
- User: Standardbenutzer mit eingeschränkten Berechtigungen
- Customer: Zugriff auf ein spezielles Kundendashboard
- Company: Zugriff auf ein spezielles Unternehmens-Dashboard
2. Datenmodell erstellen
2.1 Role-Modell und Migration
php artisan make:model Role -m
Migration für Rollen anpassen:
// database/migrations/xxxx_xx_xx_create_roles_table.php
Schema::create('roles', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->string('description')->nullable();
$table->timestamps();
});
2.2 Pivot-Tabelle für Benutzer-Rollen
php artisan make:migration create_role_user_table
Migration für Pivot-Tabelle anpassen:
// database/migrations/xxxx_xx_xx_create_role_user_table.php
Schema::create('role_user', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->foreignId('role_id')->constrained()->onDelete('cascade');
$table->timestamps();
$table->unique(['user_id', 'role_id']);
});
3. Modelle anpassen
3.1 Role-Modell erweitern:
// app/Models/Role.php
namespace AppModels;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Role extends Model
{
use HasFactory;
protected $fillable = [
'name',
'slug',
'description',
];
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class);
}
}
3.2 User-Modell erweitern:
// app/Models/User.php (Ergänzungen)
// ...
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
// ...
/**
* Die Rollen, die dem Benutzer zugewiesen sind.
*/
public function roles(): BelongsToMany
{
return $this->belongsToMany(Role::class);
}
/**
* Prüfen, ob der Benutzer eine bestimmte Rolle hat.
*/
public function hasRole(string $role): bool
{
return $this->roles()->where('slug', $role)->exists();
}
/**
* Prüfen, ob der Benutzer eine der angegebenen Rollen hat.
*/
public function hasAnyRole(array $roles): bool
{
return $this->roles()->whereIn('slug', $roles)->exists();
}
/**
* Prüfen, ob der Benutzer alle angegebenen Rollen hat.
*/
public function hasAllRoles(array $roles): bool
{
return $this->roles()->whereIn('slug', $roles)->count() === count($roles);
}
4. Seeder für Rollen und Benutzer erstellen
4.1 RoleSeeder
php artisan make:seeder RoleSeeder
// database/seeders/RoleSeeder.php
namespace DatabaseSeeders;
use AppModelsRole;
use Illuminate\Database\Seeder;
class RoleSeeder extends Seeder
{
public function run(): void
{
$roles = [
[
'name' => 'Admin',
'slug' => 'admin',
'description' => 'Administrator mit vollen Berechtigungen',
],
[
'name' => 'User',
'slug' => 'user',
'description' => 'Standardbenutzer mit eingeschränkten Berechtigungen',
],
[
'name' => 'Customer',
'slug' => 'customer',
'description' => 'Kunde mit Zugang zum Kunden-Dashboard',
],
[
'name' => 'Company',
'slug' => 'company',
'description' => 'Unternehmen mit Zugang zum Unternehmens-Dashboard',
],
];
foreach ($roles as $role) {
Role::create($role);
}
}
}
4.2 UserSeeder
php artisan make:seeder UserSeeder
// database/seeders/UserSeeder.php
namespace Database\Seeders;
use App\Models\Role;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
class UserSeeder extends Seeder
{
public function run(): void
{
// Testbenutzer mit bekanntem Passwort erstellen
$password = Hash::make('password');
// Admin-Benutzer erstellen
$admin = User::create([
'name' => 'Admin',
'email' => 'admin@example.com',
'password' => $password,
]);
// Standard-Benutzer erstellen
$user = User::create([
'name' => 'User',
'email' => 'user@example.com',
'password' => $password,
]);
// Customer-Benutzer erstellen
$customer = User::create([
'name' => 'Customer',
'email' => 'customer@example.com',
'password' => $password,
]);
// Company-Benutzer erstellen
$company = User::create([
'name' => 'Company',
'email' => 'company@example.com',
'password' => $password,
]);
// Rollen abrufen
$adminRole = Role::where('slug', 'admin')->first();
$userRole = Role::where('slug', 'user')->first();
$customerRole = Role::where('slug', 'customer')->first();
$companyRole = Role::where('slug', 'company')->first();
// Rollen zuweisen
if ($adminRole) $admin->roles()->attach($adminRole);
if ($userRole) $user->roles()->attach($userRole);
if ($customerRole) $customer->roles()->attach($customerRole);
if ($companyRole) $company->roles()->attach($companyRole);
}
}
5. Middleware für Rollenbeschränkungen
php artisan make:middleware CheckRole
// app/Http/Middleware/CheckRole.php
namespace AppHttpMiddleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\Http\Foundation\Response;
class CheckRole
{
public function handle(Request $request, Closure $next, string ...$roles): Response
{
if (! $request->user()) {
return redirect()->route('login');
}
if (empty($roles)) {
return $next($request);
}
foreach ($roles as $role) {
if ($request->user()->hasRole($role)) {
return $next($request);
}
}
abort(403, 'Unzureichende Berechtigungen.');
}
}
5.1 Middleware registrieren
php artisan make:provider KernelServiceProvider
// app/Providers/KernelServiceProvider.php
namespace AppProviders;
use App\Http\Middleware\CheckRole;
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Support\ServiceProvider;
class KernelServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->bind('role.middleware', function () {
return new CheckRole();
});
}
public function boot(): void
{
$router = $this->app['router'];
$router->aliasMiddleware('role', CheckRole::class);
}
}
6. Livewire-Komponenten für die Dashboards
6.1 Admin-Dashboard
php artisan livewire:make AdminDashboard
// app/Livewire/AdminDashboard.php
namespace AppLivewire;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use LivewireComponent;
class AdminDashboard extends Component
{
public function mount()
{
// Wir lassen die Middleware die Zugriffsrechte prüfen
}
public function render()
{
$users = User::with('roles')->get();
$isAdmin = Auth::user()->hasRole('admin');
return view('livewire.admin-dashboard', [
'users' => $users,
'isAdmin' => $isAdmin,
]);
}
}
6.2 Customer-Dashboard
php artisan livewire:make CustomerDashboard
// app/Livewire/CustomerDashboard.php
namespace AppLivewire;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
class CustomerDashboard extends Component
{
public function mount()
{
// Wir lassen die Middleware die Zugriffsrechte prüfen
}
public function render()
{
return view('livewire.customer-dashboard', [
'user' => Auth::user(),
]);
}
}
6.3 Company-Dashboard
php artisan livewire:make CompanyDashboard
// app/Livewire/CompanyDashboard.php
namespace AppLivewire;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
class CompanyDashboard extends Component
{
public function mount()
{
// Wir lassen die Middleware die Zugriffsrechte prüfen
}
public function render()
{
return view('livewire.company-dashboard', [
'user' => Auth::user(),
]);
}
}
6.4 Rollenverwaltung
php artisan livewire:make RoleManagement
// app/Livewire/RoleManagement.php
namespace AppLivewire;
use App\Models\Role;
use App\Models\User;
use Illuminate\Support\Facade\Auth;
use Livewire\Component;
use Livewire\WithPagination;
class RoleManagement extends Component
{
use WithPagination;
public $selectedUser;
public $roles = [];
public $userRoles = [];
public $search = '';
protected $rules = [
'userRoles' => 'required|array|min:1',
];
public function mount()
{
// Wir lassen die Middleware die Zugriffsrechte prüfen
$this->roles = Role::all();
}
public function selectUser($userId)
{
$this->selectedUser = User::with('roles')->find($userId);
$this->userRoles = $this->selectedUser->roles->pluck('id')->toArray();
}
public function updateUserRoles()
{
$this->validate();
if ($this->selectedUser) {
$this->selectedUser->roles()->sync($this->userRoles);
$this->selectedUser = User::with('roles')->find($this->selectedUser->id);
session()->flash('message', 'Rollen erfolgreich aktualisiert.');
}
}
public function render()
{
$users = User::with('roles')
->where('name', 'like', "%{$this->search}%")
->orWhere('email', 'like', "%{$this->search}%")
->paginate(10);
return view('livewire.role-management', [
'users' => $users,
]);
}
}
7. Blade-Views für die Dashboards
7.1 Admin/User Dashboard
// resources/views/livewire/admin/admin-dashboard.blade.php
<div class="p-6 bg-white border-b border-gray-200 rounded-lg shadow-sm">
<h1 class="text-2xl font-semibold mb-6">Admin/User Dashboard</h1>
<div class="mb-8">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="p-4 bg-blue-50 rounded-lg border border-blue-200">
<h3 class="text-lg font-medium text-blue-800">Benutzer</h3>
<p class="text-3xl font-bold">{{ $users->count() }}</p>
</div>
@if($isAdmin)
<div class="p-4 bg-green-50 rounded-lg border border-green-200">
<h3 class="text-lg font-medium text-green-800">Rollen</h3>
<p class="text-3xl font-bold">{{ AppModelsRole::count() }}</p>
</div>
@endif
<div class="p-4 bg-purple-50 rounded-lg border border-purple-200">
<h3 class="text-lg font-medium text-purple-800">Meine Rolle</h3>
<p class="text-xl font-semibold">
{{ auth()->user()->roles->pluck('name')->join(', ') }}
</p>
</div>
</div>
</div>
<div class="mb-6">
<h2 class="text-xl font-semibold mb-3">Benutzerübersicht</h2>
<div class="overflow-x-auto">
<table class="min-w-full bg-white border border-gray-200">
<thead>
<tr>
<th class="px-4 py-2 text-left bg-gray-50 border-b">Name</th>
<th class="px-4 py-2 text-left bg-gray-50 border-b">Email</th>
<th class="px-4 py-2 text-left bg-gray-50 border-b">Rollen</th>
</tr>
</thead>
<tbody>
@foreach($users as $user)
<tr class="border-b hover:bg-gray-50">
<td class="px-4 py-2">{{ $user->name }}</td>
<td class="px-4 py-2">{{ $user->email }}</td>
<td class="px-4 py-2">
<div class="flex flex-wrap gap-1">
@foreach($user->roles as $role)
<span class="px-2 py-1 text-xs rounded-full
@if($role->slug === AppEnumsRoleType::ADMIN) bg-red-100 text-red-800
@elseif($role->slug === AppEnumsRoleType::USER) bg-blue-100 text-blue-800
@elseif($role->slug === AppEnumsRoleType::CUSTOMER) bg-green-100 text-green-800
@elseif($role->slug === AppEnumsRoleType::COMPANY) bg-purple-100 text-purple-800
@endif">
{{ $role->name }}
</span>
@endforeach
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@if($isAdmin)
<div class="mt-8">
<div class="flex items-center justify-between mb-4">
<h2 class="text-xl font-semibold">Admin-Bereich</h2>
<a href="{{ route('role.management') }}" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">
Rollen verwalten
</a>
</div>
<div class="p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
<p class="text-yellow-800">
Dieser Bereich ist nur für Administratoren sichtbar. Hier können Sie auf erweiterte Funktionen zugreifen.
</p>
</div>
</div>
@endif
</div>
7.2 Customer Dashboard
// resources/views/livewire/customer/customer-dashboard.blade.php
<div class="p-6 bg-white border-b border-gray-200 rounded-lg shadow-sm">
<h1 class="text-2xl font-semibold mb-6">Kunden-Dashboard</h1>
<div class="mb-8">
<div class="p-6 bg-green-50 rounded-lg border border-green-200">
<h2 class="text-xl font-medium text-green-800 mb-4">Willkommen, {{ $user->name }}!</h2>
<p class="text-green-700 mb-3">
Sie sind als <strong>Kunde</strong> angemeldet und haben Zugriff auf alle kundenbezogenen Funktionen und Informationen.
</p>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<!-- Kundenspezifischer Inhalt -->
</div>
</div>
7.3 Company Dashboard
// resources/views/livewire/company/company-dashboard.blade.php
<div class="p-6 bg-white border-b border-gray-200 rounded-lg shadow-sm">
<h1 class="text-2xl font-semibold mb-6">Unternehmens-Dashboard</h1>
<div class="mb-8">
<div class="p-6 bg-purple-50 rounded-lg border border-purple-200">
<h2 class="text-xl font-medium text-purple-800 mb-4">Willkommen, {{ $user->name }}!</h2>
<p class="text-purple-700 mb-3">
Sie sind als <strong>Unternehmen</strong> angemeldet und haben Zugriff auf alle unternehmensbezogenen Funktionen und Informationen.
</p>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<!-- Unternehmensspezifischer Inhalt -->
</div>
</div>
7.4 Rollenverwaltungsansicht
// resources/views/livewire/admin/role-management.blade.php
<div class="p-6 bg-white border-b border-gray-200 rounded-lg shadow-sm">
<h1 class="text-2xl font-semibold mb-6">Rollenverwaltung</h1>
@if(session()->has('message'))
<div class="mb-4 p-4 bg-green-50 border border-green-200 text-green-800 rounded-lg">
{{ session('message') }}
</div>
@endif
<div class="mb-6">
<div class="flex items-center justify-between mb-4">
<h2 class="text-xl font-semibold">Benutzer</h2>
<div class="flex">
<input type="text" wire:model.debounce.300ms="search" class="px-4 py-2 border rounded-l-md focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
placeholder="Suche nach Name oder E-Mail...">
<button class="px-4 py-2 bg-indigo-600 text-white rounded-r-md hover:bg-indigo-700">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd" />
</svg>
</button>
</div>
</div>
<!-- Detaillierte Benutzer- und Rollentabelle mit RoleType-Konstanten -->
</div>
</div>
8. RoleType-Enum für Rollenkonstanten
Um Hardcoding von Rollenbezeichnungen zu vermeiden, wurde ein Enum für die Rollenkonstanten erstellt:
// app/Enums/RoleType.php
namespace App\Enums;
class RoleType
{
public const ADMIN = 'admin';
public const USER = 'user';
public const CUSTOMER = 'customer';
public const COMPANY = 'company';
}
Diese Konstanten werden in den Views und im PHP-Code für konsistente Rollenreferenzen verwendet.
9. Routen definieren
// routes/web.php
<?php
use App\Enums\RoleType;
use App\Livewire\Admin\AdminDashboard;
use App\Livewire\Admin\RoleManagement;
use App\Livewire\Company\CompanyDashboard;
use App\Livewire\Customer\CustomerDashboard;
use Illuminate\Support\Facades\Route;
use App\Models\User;
// Öffentliche Dashboard-Route als Fallback
Route::view('dashboard', 'dashboard')
->middleware(['auth', 'verified'])
->name('dashboard');
// Rollenbasierte Dashboards mit RoleType-Konstanten
Route::middleware(['auth', 'role:'.RoleType::ADMIN.','.RoleType::USER])->group(function () {
Route::get('/admin/dashboard', AdminDashboard::class)->name('admin.dashboard');
});
Route::middleware(['auth', 'role:'.RoleType::ADMIN])->group(function () {
Route::get('/admin/roles', RoleManagement::class)->name('role.management');
});
Route::middleware(['auth', 'role:'.RoleType::CUSTOMER])->group(function () {
Route::get('/customer/dashboard', CustomerDashboard::class)->name('customer.dashboard');
});
Route::middleware(['auth', 'role:'.RoleType::COMPANY])->group(function () {
Route::get('/company/dashboard', CompanyDashboard::class)->name('company.dashboard');
});
// Nach dem Login zur richtigen Dashboard-Seite umleiten
Route::get('/dashboard-redirect', function() {
$user = auth()->user();
// Benutzer-Rollen-Cache aktualisieren, um sicherzustellen, dass wir aktuelle Daten haben
$user = User::with('roles')->find($user->id);
if (!$user || $user->roles->isEmpty()) {
// Wenn Benutzer keine Rollen hat, zum Standard-Dashboard weiterleiten
return redirect()->route('dashboard');
}
// Überprüfe, ob der Benutzer bestimmte Rollen hat
$roles = $user->roles->pluck('slug')->toArray();
if (in_array(RoleType::ADMIN, $roles) || in_array(RoleType::USER, $roles)) {
return redirect()->route('admin.dashboard');
} elseif (in_array(RoleType::CUSTOMER, $roles)) {
return redirect()->route('customer.dashboard');
} elseif (in_array(RoleType::COMPANY, $roles)) {
return redirect()->route('company.dashboard');
}
// Fallback, wenn keine passende Rolle gefunden wurde
return redirect()->route('dashboard');
})->middleware('auth')->name('dashboard.redirect');
10. Login-Weiterleitung anpassen
// app/Livewire/Auth/Login.php (Anpassung)
public function login(): void
{
$this->validate();
$this->ensureIsNotRateLimited();
if (! Auth::attempt(['email' => $this->email, 'password' => $this->password], $this->remember)) {
RateLimiter::hit($this->throttleKey());
throw ValidationException::withMessages([
'email' => __('auth.failed'),
]);
}
RateLimiter::clear($this->throttleKey());
Session::regenerate();
$this->redirectIntended(default: route('dashboard.redirect', absolute: false), navigate: true);
}
11. Datenbank-Migration und Seeding
# Migration durchführen
php artisan migrate:fresh
# Rollen erstellen
php artisan db:seed --class=RoleSeeder
# Benutzer erstellen und Rollen zuweisen
php artisan db:seed --class=UserSeeder
12. Fehlerbehebung bei Weiterleitungsschleifen
Bei der Implementierung traten Weiterleitungsschleifen auf, die durch folgende Maßnahmen behoben wurden:
- Entfernung redundanter Zugriffskontrollen:
- Redundante Prüfungen in den
mount()
-Methoden der Livewire-Komponenten wurden entfernt. - Die Zugriffskontrollen wurden vollständig der Middleware überlassen.
- Redundante Prüfungen in den
- Optimierung der KernelServiceProvider:
- Die automatische Anwendung der CheckRole-Middleware auf alle Routen wurde entfernt, um Weiterleitungsschleifen zu vermeiden.
- Verbesserung der Dashboard-Weiterleitung:
- Die Rollenerkennung wurde verbessert, um sicherzustellen, dass die Benutzer zum richtigen Dashboard weitergeleitet werden.
- Ein expliziter Fallback für Benutzer ohne Rollen wurde hinzugefügt.
13. Testbenutzerzugänge
Nach der Implementierung stehen die folgenden Benutzer zur Verfügung:
- Admin
- E-Mail: admin@example.com
- Passwort: password
- Rolle: admin
- User
- E-Mail: user@example.com
- Passwort: password
- Rolle: user
- Customer
- E-Mail: customer@example.com
- Passwort: password
- Rolle: customer
- Company
- E-Mail: company@example.com
- Passwort: password
- Rolle: company
Fazit
Das implementierte Multi-User-Rollenverwaltungssystem ermöglicht eine differenzierte Zugriffskontrolle für verschiedene Benutzertypen, jeweils mit eigenen Dashboards und angepassten Funktionen. Die Implementierung nutzt Laravel 12 mit Livewire für die Frontend-Komponenten und sorgt für eine klare Trennung der Benutzerrollen.