A role is a starting point, not the whole decision An admin, manager, and customer may all have different access to the same screen. A role check answers o...
A role is a starting point, not the whole decision
An admin, manager, and customer may all have different access to the same screen. A role check answers only part of the question. The application also needs to know which resource is being accessed, who owns it, and whether the current tenant or organization is allowed to see it.
Consider an invoice in a multi organization portal. A manager may be allowed to approve invoices, but only for their own organization. A route guarded by "manager" still exposes a cross tenant invoice if the record is loaded without a tenant boundary.
Separate roles, permissions, and ownership
Use a role to describe a broad responsibility, such as finance manager. Use a permission for an action, such as invoice.approve. Use a policy to decide whether the authenticated user may take that action on this particular invoice. The policy can check organization membership, invoice state, and explicit exceptions.
Do not rely on a hidden button as an authorization control. The same endpoint can be called directly. Check authorization at the controller or action boundary, and use the policy again where background jobs or other entry points can perform the operation.
Put resource rules in a policy
The following shape illustrates the decision; adapt model names and relationships to the application:
public function approve(User $user, Invoice $invoice): bool
{
return $user->organization_id === $invoice->organization_id
&& $user->hasPermission('invoice.approve')
&& $invoice->status === 'pending';
}
The organization comparison and permission method are application specific. A production policy should also account for suspended users, delegated authority, and any approval separation rules the business actually uses. Laravel's authorization documentation covers policy registration and controller checks.
Scope the query before authorizing the action
Authorization is not a replacement for safe data retrieval. If a user lists invoices, filter the query to the organizations they may access. Then authorize the action on the selected invoice. Both steps matter: query scope limits accidental disclosure in lists and search, while the policy protects the individual operation.
For a tenant based application, include tenant ID in indexes and unique constraints where the identifier is tenant scoped. Do not trust a tenant ID sent by the browser. Resolve it from the authenticated context and verify membership before using it in a query.
Design admin exceptions deliberately
A global administrator may need cross organization access. Express that as an explicit policy branch with audit logging, rather than scattered shortcuts. Record who used the exception, what resource they touched, and why the action was permitted. An exception that bypasses a policy silently is difficult to review later.
Avoid granting broad permission simply because the UI has one "admin" label. Customer support, billing administration, and system maintenance often need different capabilities.
Test denial paths
Test a permitted user on their own invoice, a permitted role on another tenant's invoice, a user with the wrong role on their own invoice, a suspended account, and an invoice already approved. Assert both HTTP response and data integrity. A denied request must not partially modify the record or dispatch a background job.
Also test list endpoints, exports, search suggestions, and file downloads. They are frequent sources of authorization gaps because they may bypass the main detail route.
Common mistakes
- Checking roles in Blade but not at the endpoint.
- Fetching a global record by ID before applying tenant scope.
- Letting a browser supplied organization ID select the access boundary.
- Treating a null permission value as allowed.
- Giving background jobs a system identity without checking the initiating user's authority.
Conclusion
Reliable access control has three layers: scoped retrieval, action authorization, and focused tests for denial cases. Keep the decision close to the resource and make exceptions reviewable. See our web and SaaS development and AI automation pages for systems where data boundaries are central.
References