Authorization (Roles & Permissions)

May 30, 2026 · View on GitHub

AdminLTE for Laravel ships an optional, dependency-free RBAC layer — roles, permissions, a HasRoles trait on your User model, route middleware, a permission-aware Gate, and a management UI for users and roles. There is no third-party package to install: everything is published into your app as plain Laravel code you own and can edit.

It's scaffolded in one command:

php artisan adminlte:scaffold rbac --seed

RBAC is entirely optional. The package detects whether you've published it (via class_exists) and only wires policies, middleware aliases, and the Gate when the classes are present. Apps that never scaffold rbac are unaffected.


What adminlte:scaffold rbac publishes

ArtifactDestination
Migration (4 tables)database/migrations/{timestamp}_create_adminlte_rbac_tables.php
Role modelapp/Models/Role.php
Permission modelapp/Models/Permission.php
HasRoles traitapp/Models/Concerns/HasRoles.php
RoleMiddlewareapp/Http/Middleware/RoleMiddleware.php
PermissionMiddlewareapp/Http/Middleware/PermissionMiddleware.php
Seederdatabase/seeders/AdminLteRbacSeeder.php
Factoriesdatabase/factories/{Role,Permission}Factory.php
Controllersapp/Http/Controllers/AdminLte/{UserController,RoleController}.php
Viewsresources/views/adminlte/{users,roles}/ (index, create, edit)
Routesusers + roles appended to the managed group in routes/web.php

In addition, the command edits your User model to add the trait (idempotent — safe to re-run):

use App\Models\Concerns\HasRoles;

class User extends Authenticatable
{
    use HasRoles;
    // ...
}

Database tables

TablePurpose
adminlte_rolesid, name, label, timestamps
adminlte_permissionsid, name, label, timestamps
adminlte_role_userpivot: role ↔ user
adminlte_permission_rolepivot: permission ↔ role

How the package wires it up

When the RBAC classes exist, AdminLteServiceProvider does three things at boot (registerAuthorization()):

  1. Registers model policies for the scaffolded resources (only those whose model and policy classes both exist):

    ModelPolicy
    App\Models\MessageMessagePolicy
    App\Models\ProjectProjectPolicy
    App\Models\EventEventPolicy
    App\Models\KanbanCardKanbanCardPolicy
    App\Models\ConversationConversationPolicy
  2. Aliases the middleware so you can use role: and permission: on routes:

    Route::middleware('role:admin')->group(/* ... */);
    Route::middleware('permission:manage-users')->group(/* ... */);
    
  3. Registers a permission-aware Gate::before so abilities resolve against permissions automatically:

    Gate::before(function ($user, string $ability) {
        if ($user->isAdmin()) return true;                       // admins pass everything
        if ($user->hasPermission($ability)) return true;         // e.g. @can('manage-projects')
        return null;                                             // otherwise fall through to policies
    });
    

    This means a permission named manage-projects makes @can('manage-projects'), $user->can('manage-projects'), and a menu item's 'can' => 'manage-projects' all work with no extra code.


Using it

On the User model

The HasRoles trait adds:

MethodReturnsDescription
roles()BelongsToManyThe user's roles relationship.
hasRole(string|array $role)boolTrue if the user has any of the given role name(s).
hasPermission(string $name)boolTrue if any of the user's roles grants the permission.
assignRole(string $role)voidAttach a role by name.
isAdmin()boolShortcut for hasRole('admin').
$user->assignRole('editor');
$user->hasRole('admin');                 // false
$user->hasRole(['admin', 'editor']);     // true
$user->hasPermission('manage-projects'); // true

On routes

Route::middleware(['auth', 'role:admin'])->group(function () {
    // admin-only
});

Route::get('/reports', ReportController::class)
    ->middleware('permission:view-reports');

role: accepts multiple roles (role:admin,editor — passes if the user has any). permission: takes a single permission name.

In Blade and controllers

Because of the Gate::before hook, permissions behave like Gate abilities:

@can('manage-users')
    <a href="{{ route('adminlte.users.index') }}">Manage users</a>
@endcan
$this->authorize('manage-roles');
Gate::allows('manage-projects');

Gating the sidebar menu

Menu items support a can key, filtered by the package's GateFilter. Combined with the Gate hook above, you can gate a menu entry on a permission name:

// config/adminlte.php
['header' => 'administration'],
['text' => 'users', 'route' => 'adminlte.users.index', 'icon' => 'bi bi-people', 'can' => 'manage-users'],
['text' => 'roles', 'route' => 'adminlte.roles.index', 'icon' => 'bi bi-shield-lock', 'can' => 'manage-roles'],

Users without the permission simply never see the item. The package's default config already includes this Administration section.


Default roles & permissions (seeder)

AdminLteRbacSeeder creates the following and is run by --seed:

Permissions: view-dashboard, view-reports, manage-users, manage-roles, manage-projects, manage-mailbox, manage-kanban, manage-calendar, manage-settings.

Roles:

RoleLabelPermissions
adminAdministratorall permissions (and isAdmin() passes every gate)
editorEditorthe manage-* content permissions
viewerViewerview-dashboard, view-reports

The seeder assigns the admin role to the first user in the table, so the account you register first becomes the administrator.


Management UI

Scaffolding rbac adds two CRUD screens under the auth-protected /admin group:

PageURLRoute name
Users — list, create, edit, assign roles, delete/admin/usersadminlte.users.*
Roles — list, create, edit, assign permissions, delete/admin/rolesadminlte.roles.*

Both pages are gated by manage-users / manage-roles via the menu can keys and the controllers' authorize() calls.


Getting started

# 1. Publish the RBAC layer, run the migration, and seed roles/permissions.
php artisan adminlte:scaffold rbac --seed

# 2. Register a user — the first account becomes the admin.
#    (Scaffold auth first if you haven't: php artisan adminlte:make-auth)

# 3. Visit the management UI.
#    /admin/users  and  /admin/roles

To protect your own routes or features, either add a permission: / role: middleware, gate a menu item with 'can' => 'your-permission', or call $this->authorize('your-permission') in a controller — then create that permission in Roles → edit and assign it to a role.


Customising

  • Add a permission: create it in the Roles UI (or seeder), then reference its name anywhere a Gate ability is accepted.
  • Change the tables: edit the published migration before running migrate.
  • Change admin behaviour: the "admins pass everything" rule lives in HasRoles::isAdmin() and the Gate::before hook — both are in your app, edit freely.
  • Swap in a package (e.g. spatie/laravel-permission): because the wiring is guarded by class_exists, you can delete the published RBAC classes and the package quietly stops registering its Gate hook and middleware aliases.

See also: scaffolding.md for the deep Laravel artifacts (factories, form requests, policies, feature tests) and menu.md for the can filter.