Laravel 13 2,157 's #[Authorize] (Illuminate\Routing\Attributes\Controllers) attaches the can middleware from the controller. It takes the ability, then a route parameter, a class, or both in an array ([Comment::class, 'post']); on the class it covers every action, narrowed by only: or except:. Here OrderPolicy::view hides other people's orders behind a 404:
#[Authorize('view', 'order')]
public function show(Order $order): array { return $order->only('id', 'user_id', 'status'); }
public function view(User $user, Order $order): Response // in OrderPolicy
{
return $user->id === $order->user_id || $user->hasRole('staff')
? Response::allow() : Response::denyAsNotFound();
}$ curl 3,008 -si -b ben.jar -H "$J" $B/orders/1; echo HTTP/1.1 404 Not Found ... { "message": "Not Found" } $ try -b ben.jar $B/orders/99 404 {"message":"No query results for model [App\\Models\\Order] 99"} |
| Ben asks for Ann's order 1, then for order 99, which does not exist |
route:list -v lists Authorize:view,order on the route. Ben's HTML 404 page matched the one for order 99 byte for byte, but the JSON messages differ, so an API still leaks existence; with denyAsNotFound('Order not found.') and ->missing(fn () => abort(404, 'Order not found.')) on the route, both answered alike. Closure gates work too: refund() carries #[Authorize('refund-order', 'order')].