Your current gate-based approach is functionally correct: using a Gate and passing both the authenticated user and the $author to check if the requestor is allowed to see the tickets works and is safe. However, the best practice in Laravel is to use policies for model-related resource access, especially for index endpoints. Policies make your intent more explicit, use Laravel's conventions, and are designed for organizing model-related authorization logic.
Recommended Approach
- Create a Policy for the Ticket model, for example:
// app/Policies/TicketPolicy.php
public function viewAny(User $user, User $author)
{
// Only allow if the user is requesting their own tickets
// and has the proper permission
return $user->hasPermissionTo('view_ticket') && $user->id === $author->id;
}
Note: The second parameter is custom; see usage below.
- Register the Policy for the Ticket model if you haven't already (in AuthServiceProvider):
// app/Providers/AuthServiceProvider.php
protected $policies = [
\App\Models\Ticket::class => \App\Policies\TicketPolicy::class,
];
- Call the Policy in Your Controller (using
authorize):
// app/Http/Controllers/AuthorTicketController.php
public function index(Request $request, User $author)
{
// This will call TicketPolicy@viewAny($user, $author)
$this->authorize('viewAny', [Ticket::class, $author]);
$filters = $request->only(['per_page']);
$tickets = TicketsService::getAuthorTickets($author, $filters);
return TicketResource::collection($tickets);
}
authorize('viewAny', [Ticket::class, $author]);tells Laravel to use your custom signature for the policy.
Why?
- Policies are made exactly for this kind of resource-based access.
- You leverage the
viewAnyconvention for listing resources. - If you ever need to introduce team/organization features or let admins view others' tickets, you can adapt the policy accordingly without touching scattered gates.
- It keeps your authorization logic centralized and discoverable.
Summary
- YES, a policy is preferred here over a Gate, because you’re authorizing access to a model collection.
- Your index method should call
authorize('viewAny', [Ticket::class, $author])and put the logic in aviewAnymethod on the policy. - Only use Gates for "one-off" or non-model/global checks.
If you want, you can remove the Gate definition entirely and move all logic to your TicketPolicy.
Let me know if you want a complete example in one file!