Loading
Please wait...

Laravel E-Learning Rebuild

1

Part 1: Introduction & Why I'm Upgrading

2

Part 2: Setting Up Laravel 11 + Filament

3

Part 3: Database Schema Design

4

Part 4: Authentication & User Roles

5

Part 5: Building the Exam System

6

Part 6: Real-Time Features

7

Part 7: Testing Strategy

8

Part 8: Deployment

6 Januari 2026

Laravel E-Learning Part 4: Authentication & User Roles

Implementing role-based authentication with Admin, Teacher, and Student roles using Spatie Permission and Filament's multi-panel setup.

4 min read


Role Structure

Our e-learning platform has three user types:

RolePermissions
AdminFull system access, manage users, view all data
TeacherManage subjects, create exams, view student results
StudentTake exams, view own results

Step 1: Configure Spatie Permission

Update your User model to use the HasRoles trait:

<?php

namespace App\Models;

use Filament\Models\Contracts\FilamentUser;
use Filament\Panel;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable implements FilamentUser
{
    use HasFactory, Notifiable, HasRoles;

    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    protected $hidden = [
        'password',
        'remember_token',
    ];

    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed',
        ];
    }

    public function canAccessPanel(Panel $panel): bool
    {
        return match ($panel->getId()) {
            'admin' => $this->hasRole('admin'),
            'teacher' => $this->hasRole(['admin', 'teacher']),
            'student' => $this->hasRole(['admin', 'student']),
            default => false,
        };
    }
}

Step 2: Create Roles and Permissions Seeder

php artisan make:seeder RolePermissionSeeder
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;

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

        // Define permissions
        $permissions = [
            // User management
            'view_users',
            'create_users',
            'edit_users',
            'delete_users',
            
            // Subject management
            'view_subjects',
            'create_subjects',
            'edit_subjects',
            'delete_subjects',
            
            // Exam management
            'view_exams',
            'create_exams',
            'edit_exams',
            'delete_exams',
            
            // Question management
            'view_questions',
            'create_questions',
            'edit_questions',
            'delete_questions',
            
            // Results
            'view_all_results',
            'view_own_results',
            
            // Take exams
            'take_exams',
        ];

        foreach ($permissions as $permission) {
            Permission::create(['name' => $permission]);
        }

        // Create roles and assign permissions
        $adminRole = Role::create(['name' => 'admin']);
        $adminRole->givePermissionTo(Permission::all());

        $teacherRole = Role::create(['name' => 'teacher']);
        $teacherRole->givePermissionTo([
            'view_subjects', 'create_subjects', 'edit_subjects',
            'view_exams', 'create_exams', 'edit_exams', 'delete_exams',
            'view_questions', 'create_questions', 'edit_questions', 'delete_questions',
            'view_all_results',
        ]);

        $studentRole = Role::create(['name' => 'student']);
        $studentRole->givePermissionTo([
            'view_subjects',
            'view_exams',
            'take_exams',
            'view_own_results',
        ]);
    }
}

Run the seeder:

php artisan db:seed --class=RolePermissionSeeder

Step 3: Create Teacher Panel

php artisan make:filament-panel teacher

Configure app/Providers/Filament/TeacherPanelProvider.php:

<?php

namespace App\Providers\Filament;

use Filament\Http\Middleware\Authenticate;
use Filament\Http\Middleware\DisableBladeIconComponents;
use Filament\Http\Middleware\DispatchServingFilamentEvent;
use Filament\Pages;
use Filament\Panel;
use Filament\PanelProvider;
use Filament\Support\Colors\Color;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Cookie\Middleware\EncryptCookies;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
use Illuminate\Routing\Middleware\SubstituteBindings;
use Illuminate\Session\Middleware\AuthenticateSession;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\View\Middleware\ShareErrorsFromSession;

class TeacherPanelProvider extends PanelProvider
{
    public function panel(Panel $panel): Panel
    {
        return $panel
            ->id('teacher')
            ->path('teacher')
            ->login()
            ->colors([
                'primary' => Color::Green,
            ])
            ->brandName('E-Learning Teacher')
            ->discoverResources(in: app_path('Filament/Teacher/Resources'), for: 'App\\Filament\\Teacher\\Resources')
            ->discoverPages(in: app_path('Filament/Teacher/Pages'), for: 'App\\Filament\\Teacher\\Pages')
            ->pages([
                Pages\Dashboard::class,
            ])
            ->middleware([
                EncryptCookies::class,
                AddQueuedCookiesToResponse::class,
                StartSession::class,
                AuthenticateSession::class,
                ShareErrorsFromSession::class,
                VerifyCsrfToken::class,
                SubstituteBindings::class,
                DisableBladeIconComponents::class,
                DispatchServingFilamentEvent::class,
            ])
            ->authMiddleware([
                Authenticate::class,
            ]);
    }
}

Step 4: Create Student Panel

php artisan make:filament-panel student

Configure app/Providers/Filament/StudentPanelProvider.php:

<?php

namespace App\Providers\Filament;

use Filament\Http\Middleware\Authenticate;
use Filament\Http\Middleware\DisableBladeIconComponents;
use Filament\Http\Middleware\DispatchServingFilamentEvent;
use Filament\Pages;
use Filament\Panel;
use Filament\PanelProvider;
use Filament\Support\Colors\Color;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Cookie\Middleware\EncryptCookies;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
use Illuminate\Routing\Middleware\SubstituteBindings;
use Illuminate\Session\Middleware\AuthenticateSession;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\View\Middleware\ShareErrorsFromSession;

class StudentPanelProvider extends PanelProvider
{
    public function panel(Panel $panel): Panel
    {
        return $panel
            ->id('student')
            ->path('student')
            ->login()
            ->registration() // Allow student self-registration
            ->colors([
                'primary' => Color::Purple,
            ])
            ->brandName('E-Learning Student')
            ->discoverResources(in: app_path('Filament/Student/Resources'), for: 'App\\Filament\\Student\\Resources')
            ->discoverPages(in: app_path('Filament/Student/Pages'), for: 'App\\Filament\\Student\\Pages')
            ->pages([
                Pages\Dashboard::class,
            ])
            ->middleware([
                EncryptCookies::class,
                AddQueuedCookiesToResponse::class,
                StartSession::class,
                AuthenticateSession::class,
                ShareErrorsFromSession::class,
                VerifyCsrfToken::class,
                SubstituteBindings::class,
                DisableBladeIconComponents::class,
                DispatchServingFilamentEvent::class,
            ])
            ->authMiddleware([
                Authenticate::class,
            ]);
    }
}

Step 5: Auto-Assign Student Role on Registration

Create an observer or use the creating event in User model:

// In User model, add:
protected static function booted(): void
{
    static::created(function (User $user) {
        if (!$user->hasAnyRole(['admin', 'teacher'])) {
            $user->assignRole('student');
        }
    });
}

Step 6: Create User Resource for Admin

php artisan make:filament-resource User --generate

Update app/Filament/Resources/UserResource.php:

<?php

namespace App\Filament\Resources;

use App\Filament\Resources\UserResource\Pages;
use App\Models\User;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Support\Facades\Hash;
use Spatie\Permission\Models\Role;

class UserResource extends Resource
{
    protected static ?string $model = User::class;
    protected static ?string $navigationIcon = 'heroicon-o-users';
    protected static ?string $navigationGroup = 'User Management';

    public static function form(Form $form): Form
    {
        return $form
            ->schema([
                Forms\Components\TextInput::make('name')
                    ->required()
                    ->maxLength(255),
                Forms\Components\TextInput::make('email')
                    ->email()
                    ->required()
                    ->unique(ignoreRecord: true)
                    ->maxLength(255),
                Forms\Components\TextInput::make('password')
                    ->password()
                    ->dehydrateStateUsing(fn ($state) => Hash::make($state))
                    ->dehydrated(fn ($state) => filled($state))
                    ->required(fn (string $context): bool => $context === 'create'),
                Forms\Components\Select::make('roles')
                    ->relationship('roles', 'name')
                    ->multiple()
                    ->preload()
                    ->required(),
            ]);
    }

    public static function table(Table $table): Table
    {
        return $table
            ->columns([
                Tables\Columns\TextColumn::make('name')
                    ->searchable(),
                Tables\Columns\TextColumn::make('email')
                    ->searchable(),
                Tables\Columns\TextColumn::make('roles.name')
                    ->badge()
                    ->color(fn (string $state): string => match ($state) {
                        'admin' => 'danger',
                        'teacher' => 'success',
                        'student' => 'info',
                        default => 'gray',
                    }),
                Tables\Columns\TextColumn::make('created_at')
                    ->dateTime()
                    ->sortable(),
            ])
            ->filters([
                Tables\Filters\SelectFilter::make('roles')
                    ->relationship('roles', 'name'),
            ])
            ->actions([
                Tables\Actions\EditAction::make(),
                Tables\Actions\DeleteAction::make(),
            ]);
    }

    public static function getPages(): array
    {
        return [
            'index' => Pages\ListUsers::route('/'),
            'create' => Pages\CreateUser::route('/create'),
            'edit' => Pages\EditUser::route('/{record}/edit'),
        ];
    }
}

Panel Access URLs

Now you have three separate panels:

PanelURLRoles
Admin/adminadmin
Teacher/teacheradmin, teacher
Student/studentadmin, student

Summary

We've implemented:

  • ✅ Role-based access control with Spatie Permission
  • ✅ Three separate Filament panels
  • ✅ User management with role assignment
  • ✅ Automatic student role assignment on registration
  • ✅ Permission-based feature access

What's Next

In Part 5, we'll build the core exam system:

  • Exam CRUD in Filament
  • Question bank management
  • Taking exams as a student

Continue to Part 5: Building the Exam System →

Continue Reading

Previous article

← Previous Article

Laravel E-Learning Part 3: Database Schema Design

Next Article →

Laravel E-Learning Part 5: Building the Exam System

Next article