boone-studios/laravel-scoped-roles

Code-defined, scope-aware RBAC for multi-tenant Laravel apps — no permission database tables required.

Maintainers

Package info

github.com/boone-studios/laravel-scoped-roles

pkg:composer/boone-studios/laravel-scoped-roles

Transparency log

Statistics

Installs: 5

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.1 2026-07-11 21:57 UTC

This package is auto-updated.

Last update: 2026-07-11 21:59:20 UTC


README

License: MIT Used by Forgebit

Code-defined, scope-aware RBAC for multi-tenant Laravel apps — no permission database tables.

Roles live in a backed enum. Permissions are a static matrix. Membership is a string on your pivot. One gate covers dashboard users and API tokens.

Requirements

  • PHP 8.2+
  • Laravel 12 or 13

Installation

composer require boone-studios/laravel-scoped-roles

Publish the config:

php artisan vendor:publish --tag=scoped-roles-config

Quick start

  1. Create a role enum that implements BooneStudios\ScopedRoles\Contracts\DefinesPermissions.
  2. Implement ResolvesScopedRole (how to read the stored role string) and ResolvesPermissionScope (current tenant).
  3. Optionally bind a TokenPermissionResolver for API keys.
  4. Configure config/scoped-roles.php.
Gate::authorize('scoped-permission', ['manage_users']);

Middleware alias: scoped.permission:manage_users.

Inertia / Vue

php artisan vendor:publish --tag=scoped-roles-js

Share auth.role, auth.roleLabel, and auth.permissions from your Inertia middleware, then:

import { usePermissions } from '@/vendor/boone-studios/usePermissions'

const { can, canAny, role } = usePermissions()

Cache invalidation

Resolved roles are cached per user + scope for 60 minutes by default (config('scoped-roles.cache_ttl_minutes'), env SCOPED_ROLES_CACHE_TTL). The cache is automatically cleared on Login and Logout (see ClearCachedScopedRoles), but not on anything else — this package has no way to know when your application assigns, revokes, or changes a user's role mid-session.

If your app changes a user's role while they may still have an active session or a cached gate check in flight, you MUST manually invalidate the cache by calling one of:

  • ScopedRoleChecker::clearCachedRole(Authenticatable $user, Model $scope) — clears the cached role for one user in one scope.
  • ScopedRoleChecker::clearCachedRolesForUser(Authenticatable $user, ResolvesScopedRole $roleResolver) — clears the cached role for one user across every scope they belong to.

Otherwise the user (or their token) will keep acting under their old role for up to the full TTL.

Example: invalidating on role assignment

use BooneStudios\ScopedRoles\ScopedRoleChecker;

class MembershipService
{
    public function __construct(
        private \BooneStudios\ScopedRoles\Contracts\ResolvesScopedRole $roleResolver,
    ) {}

    public function assignRole(User $user, Organization $organization, string $role): void
    {
        $user->memberships()->updateOrCreate(
            ['organization_id' => $organization->id],
            ['role' => $role],
        );

        // Without this, the user (and any middleware/gate checks for them)
        // keeps seeing the previously cached role for up to cache_ttl_minutes.
        ScopedRoleChecker::clearCachedRole($user, $organization);
    }

    public function revokeAllRoles(User $user): void
    {
        $user->memberships()->delete();

        ScopedRoleChecker::clearCachedRolesForUser($user, $this->roleResolver);
    }
}

Differentiator

Unlike Spatie Permission / Bouncer, this package never stores permissions in the database. The matrix is code, reviewable, and identical across environments — ideal for multi-tenant SaaS APIs.

License

MIT © Boone Studios, LLC