minaremonshaker started a new conversation+100 XP
5mos ago
Hi everyone, I’m using the Spatie Laravel Permission package in my project, and I’m wondering if it’s considered a best practice to extend the default Role and Permission models and place them inside my app’s Models folder, rather than using the package’s built-in ones directly.
Would this approach make sense for better maintainability or customization, or is it generally better to stick with the package defaults unless I really need custom behavior?
minaremonshaker wrote a reply+100 XP
5mos ago
What do you mean by reguler web app do you mean app that uses blade or react ?
minaremonshaker started a new conversation+100 XP
5mos ago
Best systematic way to plan all permissions for Laravel 12 ticketing system API with Spatie?
I'm building a comprehensive ticketing system API using Laravel 12 with Spatie Laravel-Permission for role-based authorization (roles like admin, manager, agent, customer).
I've implemented controllers for:
- Tickets (CRUD)
- Roles/permissions management (assign/revoke/view for roles)
- User search/filtering
Current permissions (snake_case pattern): tickets: view_any_ticket, view_ticket, create_ticket, update_ticket, delete_ticket, assign_ticket roles: view_role_permissions, assign_role_permissions, revoke_role_permissions, update_role_permissions permissions: view_any_permission, create_permission, update_permission, delete_permission
My approach:
- Map permissions to controller actions (
create_ticket,update_ticket_status) - Policies + Gates (
TicketPolicy::viewAny()checks$user->can('view_any_ticket')) - Artisan command to seed permissions
Code example RolesPermissionsController@assign:
public function assign(Request $request, Role $role)
{
Gate::authorize('assign_permission_to_role', Permission::class);
$role = RoleService::assignRole($role, $request->permissions);
return PermissionsResource::collection(PermissionService::getPermissionsOfRole($role));
}
Struggling to identify ALL permissions without missing edge cases or over-engineering.
Questions:
What's the best systematic process to discover/plan permissions for ticketing system? (routes analysis, user stories, audit logs?)
Common permission patterns for ticketing apps?
Ticket assignment
Status changes
Comments/attachments
Escalations
Reports/SLAs?
Granularity level?
update_ticket (all fields)
OR update_ticket_priority, update_ticket_status, update_ticket_assignee?
Tools/scripts to auto-generate permission seeds from routes/controllers?
Role/permission management auth?
Direct Spatie: $user->can('view_role_permissions')
OR Policies wrapping Spatie checks?
minaremonshaker wrote a reply+100 XP
5mos ago
Hi @tykus,
Thanks for your reply. In this case, will the before callback run by default, or do I need to explicitly call it via Gate::authorize in the controller?
Gate::athorize("before" , Tickit::class)
minaremonshaker started a new conversation+100 XP
5mos ago
I'm trying to get a solid grasp on how Laravel handles route specificity when multiple routes could potentially match a given URI. From what I've read, routes are matched based on the order they're defined, with more specific (static) routes taking precedence over parameterized ones. But I'd love some clarification and examples from experienced devs.
For instance:
-
If I have Route::get('/foo/bar', 'ControllerA') defined before Route::get('/foo/{slug}', 'ControllerB'), does / foo/bar always hit ControllerA first due to specificity?
-
What if both are parameterized, like /foo/{id} vs /foo/{slug}?
-
Any gotchas with route groups, middleware, or API routes?
Also, best practices for ordering routes in routes/web.php or routes/api.php to avoid surprises? php artisan route:list helps verify, but understanding the rules upfront would save headaches in larger apps.
minaremonshaker liked a comment+100 XP
5mos ago
Yes, the Gate::before callback will bypass your TicketPolicy::view() method if it returns a non-null value.
Here's how it works:
- When an ability is being checked (e.g.
Gate::authorize('view', $ticket);), Laravel will first run anyGate::beforecallbacks. - If your
Gate::beforereturns a non-null value (trueorfalse), that value is immediately considered the final answer for the authorization check, and no further policy methods will be called for that ability check. - If your
Gate::beforereturnsnull, Laravel falls through to the associated policy method (likeview) and evaluates that as normal.
So, with your code:
Gate::before(function (User $user, $ability) {
if ($user->hasAnyPermission(['view_any_ticket'])) {
return true;
}
return null;
});
If the user has the view_any_ticket permission, Gate::before returns true, and Laravel grants access for any ability (including 'view'), without ever calling your policy's view method.
To sum up:
If Gate::before returns true, the TicketPolicy::view() method is not called and access is granted immediately.
Reference:
The official Laravel documentation on Gates says:
"If the before callback returns a non-null result that result will be considered the result of the authorization check."
So in your scenario, yes, the global Gate::before will bypass the policy method if it returns true.
minaremonshaker started a new conversation+100 XP
5mos ago
I’m defining a global Gate::before check in my AppServiceProvider like this:
Gate::before(function (User $user, $ability) {
if ($user->hasAnyPermission(['view_any_ticket'])) {
return true;
}
return null;
});
In my TicketPolicy, the view method looks like this:
public function view(User $user, Ticket $ticket): bool
{
if ($user->hasPermissionTo('view_ticket')) {
return $user->id === $ticket->user_id;
}
return false;
}
Then, in my controller, I authorize like this:
Gate::authorize('view', $ticket);
If a user has the view_any_ticket permission through a “moderator” role, will the global Gate::before return true and completely bypass the TicketPolicy::view() method?
minaremonshaker started a new conversation+100 XP
5mos ago
hi i am using spaite permissions for authorization , I want to ask what are the situation where i can assign direct permissions to users
minaremonshaker wrote a reply+100 XP
5mos ago
hi thank you for the answer , i will do that of course but i want to ask you about lary`s answer what do you think about it ?
minaremonshaker wrote a reply+100 XP
5mos ago
hi thank you for the answer ,what i want is to make access control even for the permissions model and roles model where admin user can do every thing and manger can do spacific things in the admin panel
minaremonshaker started a new conversation+100 XP
5mos ago
I'm building an admin panel using Spatie Laravel Permission and I'm confused about the "right way" to handle authorization in Permission/Role controllers.
Option A (Direct Spatie check):
if (!auth()->user()->hasPermissionTo('view_any_permission')) {
abort(403);
}
Option B (Laravel Policy):
Gate::authorize('viewAny', Permission::class);
// Policy just wraps: return $user->hasPermissionTo('view_any_permission');
I've seen both approaches but get mixed advice:
-
Spatie docs seem to favor direct checks - Permission/Role models are "internal admin tools"
-
Laravel docs push policies - Consistent authorization pattern across app
-
My current setup: Already created PermissionPolicy ✅ registered ✅ working
Questions:
-
Is PermissionPolicy truly "over-engineering" for admin screens?
-
When Spatie already integrates with Gates ($user->can()), what's the value of wrapping it in a policy?
-
Community standard: Direct Spatie checks vs Policies for Permission/Role management?
Context:
- Laravel 12, Spatie v6
- Using policies successfully for business models (PostPolicy, UserPolicy, TicketPolicy)
- Permissions follow snake_case convention (view_any_permission)
Current working code:
Gate::authorize('viewAny', Permission::class);
// Policy
public function viewAny(User $user): bool
{
return $user->hasPermissionTo('view_any_permission');
}
Should I keep the policy pattern for consistency, or switch Permission/Role controllers to direct Spatie checks for simplicity?
minaremonshaker wrote a reply+100 XP
5mos ago
Hi @glukinho, do you mean I should create a field called public and include it in my policy as well?
minaremonshaker started a new conversation+100 XP
5mos ago
In a Laravel API controller, I return a collection of tickets that belong to a specific user (“author”). The authenticated user may have multiple tickets, and I’m unsure what the best-practice authorization approach is for this index endpoint.
Goal: Allow the request only if the authenticated user has the view_ticket permission and is the same user as the $author route parameter.
What I tried:
-
Looping through each ticket and calling authorize() / Gate::authorize() per ticket.
-
Creating a dedicated gate (show-users-tickets) that receives the $author and checks permission + ownership.
class AuthorTicketController extends Controller
{
public function index(Request $request, User $author)
{
$filters = $request->only(['per_page']);
Gate::authorize('show-users-tickets', $author);
$tickets = TicketsService::getAuthorTickets($author, $filters);
return TicketResource::collection($tickets);
}
}
Gate::define('show-users-tickets', function (User $user, User $author) {
if ($user->hasPermissionTo('view_ticket')) {
return $user->id === $author->id;
}
return false;
});
Question: Is this gate-based approach correct for authorizing access to a list of tickets, or should I be using a policy (e.g., TicketPolicy@viewAny / view) and/or authorizing at the query level instead? (Laravel documentation describes policies as a way to organize authorization logic around a particular model or resource.)
minaremonshaker wrote a reply+100 XP
5mos ago
Hi, I want to prevent non-admin users from accessing other users' tickets. Each logged-in user should only see their own tickets.
For example, when I make a request to http://localhost:8000/api/authors/1/tickets, the 1 parameter represents an admin user. If this isn't the currently logged-in user, I expect to receive an unauthorized exception.
However, the issue is:
-
If user with ID 1 has no tickets, I get an empty array instead of an unauthorized exception
-
If user with ID 1 has tickets, the authorization works correctly and I get the unauthorized exception
I hope that clarifies my question.
minaremonshaker wrote a reply+100 XP
5mos ago
hi i am doing this because i am using spaite query builder in my service to for searching and feltering
minaremonshaker started a new conversation+100 XP
5mos ago
In the controller method below, I'm experiencing an authorization issue. When an admin has no tickets, I receive an empty array [] instead of an authorization exception. However, when tickets exist for the admin, I correctly get the unauthorized exception. The expected behavior is that I should receive an authorization exception whenever I try to access admin tickets, regardless of whether tickets exist or not.
NOTE: i have sloved this by creating a gate in the AppServiceProvider in the boot method as below but i don`t know if that is correct or not
Gate::define('showUserTicketList', function (User $user ,User $author) { if($user->hasPermissionTo('ShowOwnTicket')) { return $user->id === $author->id; } return false; });
-
controller method :
public function index(Request $request, User $author) { // $filters = $request->only(['per_page']); $tickets = TicketsService::findTicketsByUserId($author); foreach ($tickets as $ticket) { Gate::authorize('view', [Ticket::class, $ticket]); } return TicketResource::collection($tickets); } -
Service method:
public static function findTicketsByUserId(User $author) { return QueryBuilder::for(Ticket::class) ->whereUserId($author->id) ->paginate(); } -
Policy: (TicketPolicy)
public function viewAny(User $user): bool { if ($user->hasPermissionTo('ShowTicket')) { return true; } return false; }
minaremonshaker wrote a reply+100 XP
5mos ago
Hi, Thank you for your reply. I just wanted to let you know that I’m currently working only on the API and haven’t created the UI yet. But you’re right — I can use a checkbox group when I build it.
minaremonshaker liked a comment+100 XP
5mos ago
Use a multi select dropdown for the roles, or use a checkbox group. They both give you an array of roles.
minaremonshaker wrote a reply+100 XP
5mos ago
the contains dose not works as expected , i solved the problem by adding :input to the custom message , the :input gets the value as expected
public function messages(): array
{
return [
"roles.*.exists" => "the role :input dose not exists in our database" ,
];
}
minaremonshaker wrote a reply+100 XP
5mos ago
Hi, I followed your suggestion and am now sending the roles as a JSON array. However, I am facing another issue related to customizing the validation messages. I need to include the actual invalid values from the submitted array in the error message. When one of the array values fails validation, the error message I receive looks like the default one, but I would like to override it using the value itself.
From the documentation, I found several approaches, such as using the index or position placeholders, or using Rule::forEach, but they still rely on indexes or positions. Is there a better approach to achieve this?
https://laravel.com/docs/12.x/validation#accessing-nested-array-data
https://laravel.com/docs/12.x/validation#error-message-indexes-and-positions
data sent :
{
"roles": [
"admin",
"user",
"manager"
]
}
request :
class AssignRoleToUserRequest extends FormRequest
{
public function rules(): array
{
return [
"roles" => ['required', 'array'],
"roles.*" => ['exists:roles,name']
];
}
public function messages(): array
{
return [
"roles.*.exists" => "the roles array contains :attribute that are not exists in our database" ,
];
}
public function authorize(): bool
{
return true;
}
}
validation error message
{
"message": "the roles array contains roles.2 that are not exists in our database",
"errors": {
"roles.2": [
"the roles array contains roles.2 that are not exists in our database"
]
}
}
minaremonshaker started a new conversation+100 XP
5mos ago
I have a controller method that assigns roles to users. The request includes a string containing the roles to assign, and I need to validate that each specified role exists in the roles table using a form request. Currently, I created a custom validation rule that converts the string into an array, loops through each role, and checks if it exists in the database—failing if any do not. I’m wondering if there’s an alternative approach to achieve this validation without creating a custom rule, or if that’s the only viable solution.
- validation custom rule
class UserHasRole implements ValidationRule
{
public function __construct(protected int $user_id){}
/**
* Run the validation rule.
*
* @param Closure(string, ?string=): PotentiallyTranslatedString $fail
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$roles = explode(",", $value);
$user = \App\Models\User::find($this->user_id);
foreach ($roles as $role){
if($user->hasRole($role)){
$fail("user has role $role");
}
}
}
}
- form request
class AssignRoleToUserRequest extends FormRequest
{
public function rules(): array
{
$user_id = $this->route()->parameter('user');
return [
"roles" => ['required', 'string', new UserHasRole($user_id),]
];
}
public function authorize(): bool
{
return true;
}
}
- controller method
public function assign(AssignRoleToUserRequest $request, int $user_id)
{
}
minaremonshaker liked a comment+100 XP
5mos ago
It's completely normal.
minaremonshaker started a new conversation+100 XP
5mos ago
Hi, my routes/api.php file has become quite large, so I’d like to split the routes into separate PHP files inside the routes directory. I don’t want to register them manually in app.php—I just plan to include those files directly within api.php. Is that a good approach, or are there better alternatives?