iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
A safe multi-tenant Laravel authorization system makes four decisions in order: which user is signed in, which tenant and team are active, whether the user has the required capability, and whether that particular record satisfies tenant, ownership and state rules. Use Spatie Laravel Permission for database-backed roles and permissions, Laravel policies for resource and attribute checks, and gates for abilities that do not belong to one model. Enforce the same decision in web requests, APIs, jobs and commands so a hidden button can never become the only security boundary.
The authorization model to build
Multi-tenancy changes authorization from a single yes/no question into a scoped decision. A user may be an owner in one tenant, an editor in a second tenant and have no access to a third. Even inside one tenant, a role such as projects.update should not let a user edit a project owned by another tenant or a record locked by a compliance workflow.
Keep the responsibilities separate:
- Tenant resolution identifies the tenant from a trusted hostname, route binding or session choice and verifies that the user belongs to it.
- Team resolution identifies the active team inside that tenant and verifies team membership.
- RBAC answers whether a role grants a stable capability such as
projects.updateormembers.invite. - ABAC-style policy rules evaluate attributes such as tenant ID, owner ID, membership, record status, subscription limits or environment.
- Gates handle broad abilities that are not naturally owned by one model, such as viewing a tenant-wide audit export.
Laravel describes gates as closure-based checks and policies as classes that organize authorization around a model or resource. Its documentation recommends policies for robust applications rather than placing every rule in unrelated gate closures. That division gives you one predictable place to find each rule while still allowing a role system to be administered from a database.
Choose the package and version line before coding
Spatie Laravel Permission stores permissions and roles in database tables, registers permissions with Laravel’s Gate, and therefore works with can() and Blade @can checks. Confirm the package line against the Laravel and PHP versions already pinned by your application. The published compatibility table lists Laravel 12 and 13 with package versions ^7.0–^8.0 and PHP 8.3 or newer; older framework versions map to older package lines.
Popular on Amazon
- Record the exact Laravel, PHP and Spatie versions in your deployment documentation.
- Pin those versions in Composer rather than allowing an unreviewed major upgrade.
- Re-check the compatibility table before upgrading Laravel or PHP, then run authorization and tenancy tests against the new combination.
Laravel’s policy generator is available with:
php artisan make:policy PostPolicy
When the policy is placed in the conventional Policies directory and follows the model name plus Policy suffix, Laravel can discover it by convention. Keep one policy per protected resource where practical and inject collaborators through the service container instead of constructing them inside policy methods.
Resolve tenant and team context before loading data
The most important ordering rule is to establish context before a tenant-owned model is queried. A controller that loads a project by an unscoped ID and only then checks authorization can leak whether another tenant’s record exists, even if the final policy denies access.
1. Resolve the tenant from a trusted source
Use a tenant-aware domain, an authenticated tenant selector or a route segment that is validated against the user’s memberships. Do not treat an integer tenant ID as a secret. The Tenancy for Laravel integration notes that integer IDs are enumerable; UUIDs avoid that specific enumeration problem, but membership authorization is still required.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problems2. Verify membership and establish request state
Resolve the current user first, then confirm that the user can act in the selected tenant. Store the resulting tenant object in request-scoped context. Clear that context when the request, queued job or long-running worker task ends. Never keep the previous request’s tenant in a singleton or static property.
3. Resolve the active team
If a tenant contains teams, resolve the requested team and verify membership before querying team-owned records. For Spatie’s teams feature, set the active team with:
setPermissionsTeamId($new_team_id);
Changing the active team is not enough by itself. Spatie warns that the user’s roles and permissions relations must be unset before querying them or calling can(), hasPermissionTo() or hasRole(). Treat the team ID as request-scoped state and clear it when the tenancy context ends. Add a test that switches from team A to team B in one worker process; this catches stale relation data that ordinary one-request tests miss.
4. Scope every query
Prefer a tenant-aware repository or model scope so a missing condition is difficult to introduce:
Also popular on Amazon
$project = Project::query()
->where('tenant_id', $tenant->id)
->where('team_id', $team->id)
->findOrFail($projectId);
Route model binding must be scoped in the same way. A policy is a second line of defense, not a replacement for tenant-scoped retrieval.
Design stable permissions and tenant-owned roles
Permission names should describe capabilities, not screens or individual users. Names such as projects.view, projects.update, projects.delete and members.invite remain useful when the UI changes. Roles are bundles of those capabilities, for example owner, admin, editor and viewer.
Seed a baseline permission set for each tenant, then let an authorized tenant owner administer role membership. Do not create a new permission for every record; ownership and state belong in policies. Keep platform-level roles separate from tenant roles if your product has support staff or global operators, and make the distinction explicit in policy code.
Recommended data relationships
- User–tenant membership: identifies which tenants a user may enter and the membership status.
- Tenant–team membership: identifies which teams a user may select within a tenant.
- Role assignments: connect a user to a role in the active tenant or team.
- Permissions: define capabilities once and attach them to roles.
- Protected records: carry tenant and, where applicable, team ownership columns.
A role check alone must never authorize an object from another tenant. The role answers “may this person attempt this operation?”; the policy answers “may this person perform it on this object under its current attributes?”
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Implement policies as the ABAC boundary
Generate a policy for each protected resource and put tenant, team, ownership and state conditions there. A typical update method combines the RBAC capability with attributes:
public function update(User $user, Project $project): bool
{
return $user->can('projects.update')
&& $project->tenant_id === app(CurrentTenant::class)->id
&& $project->team_id === app(CurrentTeam::class)->id
&& ($project->owner_id === $user->id || $user->hasRole('admin'))
&& $project->status !== 'locked';
}
The exact attributes differ by product, but the sequence is consistent:
- Confirm the capability through
can()or an equivalent permission check. - Confirm that the model belongs to the resolved tenant and active team.
- Apply ownership, membership, workflow-state and other record attributes.
- Apply environmental constraints such as a read-only maintenance mode if your application has them.
For APIs that need to explain a denial, return an authorization response with a reason rather than a bare boolean. Keep sensitive details out of messages; “This project is outside your active tenant” is safer than revealing another tenant’s existence.
Also popular on Amazon
Creation and listing need different safeguards
For creation, there is no model yet, so the policy can authorize the capability and the service must assign the tenant and team from trusted context rather than request input. Ignore or reject client-supplied tenant_id values that differ from the resolved context.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteFor listing, authorize the ability and constrain the query. Calling a policy only after fetching a broad collection is both inefficient and unsafe. Every list, search, export and aggregate query needs tenant and team predicates before pagination or counting.
Use gates for cross-resource and administrative abilities
Some abilities do not belong to one model: rotating a tenant’s signing key, exporting a tenant-wide audit log or entering a support mode. Define these as gates or dedicated authorization services. The gate should still consume the resolved tenant and user context, and it should not bypass membership checks simply because the ability is “administrative.”
Keep gate definitions small. If a gate starts accepting several models and reproduces ownership rules, move those rules into the relevant policy or a domain authorization service so the same decision is reused by controllers, jobs and commands.
Configure Spatie teams without leaking permissions
Enable the package’s team-aware mode according to the version you have installed, then make the active team explicit for every request. A safe middleware sequence is:
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →- Authenticate the user.
- Resolve and authorize the tenant.
- Resolve and authorize the team within that tenant.
- Call
setPermissionsTeamId($new_team_id). - Unset any previously loaded user
rolesandpermissionsrelations. - Run the controller, policy and service logic.
- Clear the team and tenant context in a
finally-style cleanup path.
Do not switch the active team halfway through a request. If a workflow must operate on two teams, run each operation in a separate, explicitly reset context and test that no relation or cache from the first team survives.
Isolate tenant migrations and permission caches
The documented Tenancy for Laravel integration instructs teams to publish Spatie’s permission migrations into the tenant migration directory and run those migrations for tenants. Follow that integration’s migration layout instead of leaving permission tables in a landlord database by accident.
Also popular on Amazon
Roles and permissions are cached. The integration also documents changing Spatie’s permission cache key to include the tenant identifier. Without tenant-specific cache namespaces, a permission result written while tenant A is active can be reused while tenant B is active. Include both tenant and, where team assignments are distinct, team context in the cache namespace used by your tenancy design.
Cache invalidation checklist
- Change the cache key whenever the active tenant changes.
- Clear or rebuild the relevant permission cache after role or permission updates.
- Ensure queue workers and Octane-style long-running workers reset tenant and team state between jobs.
- Test a role granted in tenant A is absent in tenant B, even when both use the same role name.
- Test revocation immediately: a removed permission must not remain effective because an old cache entry survived.
Enforce the same authorization in every execution path
UI checks improve usability but are not security controls. A user can call an endpoint directly, replay a request or dispatch a job without rendering your Blade view.
Web controllers
Authorize before mutating and retrieve models through tenant-scoped queries. Use Laravel’s policy authorization methods or an explicit policy call, then pass only validated attributes to the service that performs the change.
API endpoints
Apply authentication, tenant resolution, team resolution and authorization middleware consistently. Return a uniform 403 response for an authenticated user who lacks permission and avoid leaking whether an object in another tenant exists; in some designs a 404 response is appropriate for an out-of-scope identifier.
Jobs and queued notifications
Do not serialize only a model ID and assume the worker will know its tenant. Include the tenant and team identifiers in the job payload, establish context at the start of handle(), reset Spatie’s active team, perform policy checks again, and clean up in all exit paths. A permission granted when a job was queued may have been revoked by the time it runs, so authorize at execution time.
Artisan commands and scheduled tasks
Commands that iterate tenants should establish and clear context for each iteration. Never let a failed tenant iteration continue with its context still active. Scheduled maintenance that acts across all tenants should use an explicitly privileged service account or capability and still scope every query.
Free tools Windows power users keep installed
One-click scans. No signup required.
Compare an all-policy design with RBAC plus policies
| Decision axis | Policies only | Spatie RBAC plus policies |
|---|---|---|
| Where permissions live | Mostly application code and configuration. | Capabilities and role assignments are stored in database tables; resource conditions remain in policies. |
| Tenant administrator control | Usually requires a deployment or configuration change. | Tenant owners can change approved role bundles through controlled administration. |
| Attribute conditions | Natural in policy methods. | Still handled in policy methods; roles do not replace ownership or state checks. |
| Cache behavior | Primarily application/configuration cache. | Permission cache must be isolated and invalidated per tenant and team context. |
| Auditability | Code review shows rule changes; runtime role changes need separate logging. | Database role changes can be audited, while policy changes remain code changes. |
| Migration complexity | Fewer permission tables, but more release work for rule changes. | Requires tenant-aware permission migrations, seeding and cache management. |
| Missing tenant context | Can be rejected directly by policy or service code. | Must also prevent stale team relations or cached permissions from being consulted. |
For most SaaS products, the combined model is the practical choice: RBAC provides understandable, administrable capabilities, while policies preserve precise tenant and record boundaries.
Also popular on Amazon
Test for cross-tenant and cross-team failures
Authorization tests should prove isolation, not only happy-path access. Build a matrix covering at least these cases:
- A user with
projects.viewin tenant A cannot read a tenant B project. - An editor can update an unlocked project they own but not a locked project.
- A viewer can read but cannot update or delete.
- A team A role does not apply after switching to team B in the same process.
- Changing teams unsets loaded
rolesandpermissionsrelations before a newcan()check. - A direct API request is denied even when the corresponding UI control is hidden.
- A queued job re-checks membership after a role is revoked.
- Permission cache entries for two tenants use different namespaces.
- A missing, inactive or deleted tenant membership fails before any tenant-owned query runs.
- Tenant migration setup exists for a newly provisioned tenant and does not accidentally read another tenant’s permission tables.
Run these tests under the same worker model used in production. A test suite that creates a fresh PHP process for every case may miss stale singleton, relation or active-team state that appears in Octane, queue workers or scheduled loops.
Common failure modes and fixes
Role check grants access to another tenant
Cause: the controller checks only can('projects.update'). Fix: scope retrieval by tenant and team, then require the policy’s tenant equality and ownership conditions.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Permissions from the previous team remain active
Cause: the active team changed but the user’s relations were already loaded. Fix: unset roles and permissions before any new authorization call and add a same-worker team-switch test.
Tenant A’s permission appears in tenant B
Cause: a shared permission-cache key. Fix: include the tenant identifier in the cache namespace, clear stale entries after migration or role changes, and verify the behavior with two-tenant tests.
Jobs act on the wrong tenant
Cause: a worker retained context from a previous job or the job did not carry enough context. Fix: serialize tenant and team identifiers, initialize context at job start, authorize again, and always reset state.
Security depends on hidden buttons
Cause: authorization exists only in Blade. Fix: repeat the check in the controller, API action, service, job or command that performs the operation.
Production readiness checklist
- Tenant and team are resolved and membership-checked before model queries.
- All tenant-owned tables and route bindings are scoped.
- Stable capability names are seeded and documented.
- Roles are tenant- or team-scoped and administered only by authorized owners.
- Policies enforce tenant equality, team equality, ownership, membership and record state.
- Gates are reserved for cross-resource or global abilities.
- Spatie’s active team is set per request and cleared afterward.
- User role and permission relations are unset after a team switch.
- Permission migrations run in the tenant migration location required by the tenancy integration.
- Permission-cache keys include tenant context and are invalidated after changes.
- Web, API, queue and command paths perform authorization independently of UI visibility.
- Version pins and upgrade tests cover the Laravel, PHP and Spatie compatibility combination in production.
- Logs record authorization-changing events without exposing secrets or cross-tenant data.
What this architecture gives you
The resulting decision pipeline is explicit: resolve the user, tenant and team; set and reset the active team; check the RBAC capability; evaluate policy attributes; and enforce the result everywhere data can be read or changed. This keeps tenant boundaries independent from guessable identifiers, prevents stale team relations and shared caches from crossing contexts, and lets tenant administrators manage roles without weakening resource-level rules.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

