Using SystemRoles in Your App

Blade, Gates and Policies

On this page

SystemRoles works alongside Laravel's own authorization. Connecting the two lets you use @can, $user->can(), Gate::allows() and ->can() on routes with SystemRoles permissions.

Connect SystemRoles to Laravel's Gate#

In App\Providers\AppServiceProvider::boot() (or AuthServiceProvider on Laravel 9/10):

PHP
use BabeRuka\SystemRoles\Models\SystemRolesIn;
use BabeRuka\SystemRoles\Services\SystemRolesService;
use Illuminate\Support\Facades\Gate;

public function boot(): void
{
    // The super admin passes every ability.
    Gate::before(function ($user) {
        return SystemRolesService::isSuperAdmin($user) ? true : null;
    });

    // Any ability that is a SystemRoles permission is checked against the matrix.
    Gate::after(function ($user, string $ability, ?bool $result) {
        if ($result !== null) {
            return $result;           // a policy or gate already decided
        }
        $roleId = SystemRolesService::roleIdForUser($user->getKey());
        return SystemRolesService::roleHas($roleId, $ability);
    });
}

Now every SystemRoles permission is a Laravel ability:

PHP
$user->can('invoice.approve');
Gate::authorize('invoice.approve');

Route::post('/invoices/{invoice}/approve', ...)->can('invoice.approve');

In Blade#

Blade
@can('invoice.approve')
    <button class="btn btn-success">Approve</button>
@endcan

@cannot('invoice.delete')
    <span class="text-muted">Read only</span>
@endcannot

A custom directive for roles:

PHP
use Illuminate\Support\Facades\Blade;
use BabeRuka\SystemRoles\Models\SystemRoles;
use BabeRuka\SystemRoles\Services\SystemRolesService;

Blade::if('role', function (string ...$guards) {
    $roleId = SystemRolesService::roleIdForUser(auth()->id());
    $guard  = SystemRoles::whereKey($roleId)->value('role_guard_name');
    return SystemRolesService::isSuperAdminRole($roleId) || in_array($guard, $guards, true);
});

Blade::if('superadmin', fn () => SystemRolesService::isSuperAdmin());
Blade
@role('admin', 'manager')
    <a href="{{ route('reports.index') }}">Reports</a>
@endrole

@superadmin
    <a href="{{ route('systemroles.admin.roles.index') }}">Roles &amp; permissions</a>
@endsuperadmin

Hiding a link is for convenience only. Always protect the route itself too.

In policies#

Policies answer questions about a specific record ("may this user edit this invoice?"). Combine a permission with ownership:

PHP
class InvoicePolicy
{
    public function update(User $user, Invoice $invoice): bool
    {
        $roleId = SystemRolesService::roleIdForUser($user->id);

        if (SystemRolesService::roleHas($roleId, 'invoice.update.any')) {
            return true;
        }

        return SystemRolesService::roleHas($roleId, 'invoice.update.own')
            && $invoice->owner_id === $user->id;
    }
}

The Gate::before hook above lets the super admin pass every policy too.

In API resources and JSON#

Send the user's permissions to a front end (Vue, React, mobile) once after login:

PHP
return [
    'user'        => $user->only('id', 'name', 'email'),
    'role_id'     => $roleId = SystemRolesService::roleIdForUser($user->id),
    'super_admin' => SystemRolesService::isSuperAdminRole($roleId),
    'permissions' => collect(SystemRolesService::permissionsForRole($roleId))->filter()->keys(),
];

The front end uses this list to show or hide things. The API still checks every request on the server.