Для создания ролей и назначения прав пользователей есть разные подходы. Часто можно встретить вариант, по которому создается отдельная таблица 'roles', таблица 'permissions', а так же связывающие таблицы их между собой и с таблицей пользователей. Расширения для фреймворка работают по похожему принципу. Но для простых сайтов, это, обычно, оказывается лишним.
В данной заметке о том, как организовать простую систему работы с ролями пользователей.

Для этого нам придется внести всего одно изменение в базу данных, а именно - создать поле для хранения ролей в таблице с пользователями.

В сущность User добавляем роли в виде констант:
const ROLE_USER = 1;
const ROLE_ADMIN = 10;
Для примера тут всего 2 роли.

А так же добавим метод проверки текущего пользователя - админ он или нет:
public function isAdmin(): bool
{
    return $this->role === self::ROLE_ADMIN;
}
Для всех новых ролей нужно создавать аналогичные методы.

Создаем миграцию:
php artisan make:migration AddUserRole

Содержимое класса меняем таким образом:
class AddUserRole extends Migration
{
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->unsignedSmallInteger('role')->after('status');
        });

        DB::table('users')->update([
            'role' => User::ROLE_USER,
        ]);
    }

    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('role');
        });
    }
}
Всем существующим на данный момент пользователям автоматически присваиваем роль простого пользователя.

Применяем миграцию:
php artisan migrate


В файле Providers/AuthServiceProvider.php прописываем нужные правила для существующих ролей.
Например у нас есть админка и нужно разрешить к ней доступ для ролей имеющих право с названием (например) «admin-panel»
public function boot()
{
    $this->registerPolicies();

    Gate::define('admin-panel', function (User $user) {
        return $user->isAdmin();
    });
}

Осталось только применить данное правило. Это можно сделать по-разному. Т.к. правило в данном случае глобальное - ограничивает доступ ко всей админке, можно его прописать для группы маршрутов которая обслуживает админку.
routes/web.php
Route::group(
    [
        'prefix' => 'admin',
        'middleware' => ['auth', 'can:admin-panel'],
    ],
    function () {
        //
    }
);
«auth» - алиас для посредника \App\Http\Middleware\Authenticate (прописанный в Http/Kernel.php). Он нужен для того, чтобы неавторизованного пользователя перенаправляло на страницу входа.
«can» - алиас для посредника \Illuminate\Auth\Middleware\Authorize который будет проверять права пользователей.
Через двоеточие передается параметр для посредника.

Аналогично можно прописать в нужном контроллере:
public function __construct(){
    $this->middleware('can:admin-panel');
}

Можно прописать несколько прав через запятую:
'can:admin-panel,other'

Есть возможность передавать нужные для проверки данные при создании правила, например проверить является ли текущий пользователь автором данного поста или объявления:
Правило:
Gate::define('show-advert', function (User $user, Advert $advert) {
    return $user->isAdmin() || $user->isModerator() || $advert->user_id === $user->id;
});

Проверка с передачей данных:
if (!Gate::allows('show-advert', $advert)) {
    abort(403);
}



Может показаться лишним создание правил типа:
    Gate::define('admin-panel', function (User $user) {
        return $user->isAdmin();
    });
если можно просто вставить такую проверку в нужное место:
if(!\Auth::user()->isAdmin()){
    abort(403);
}

Но это у нас только одна роль. А если ролей 3 или более, то проверка уже будет выглядеть так:
if(!(\Auth::user()->isAdmin() || \Auth::user()->isManager() || \Auth::user()->isModerator())){
    abort(403);
}
Удобнее прописывать все проверки в отдельном файле и обращаться к ним с помощью посредников.