Authorize every entrypoint
HTTP, Job, and Command boundaries authorize before their single UseCase call. A Policy answers whether an actor may act; it does not replace Domain invariants or tenant isolation.
Before you begin
Run composer run reference:test:authorization and read authorization.
Restore context before a Job call
examples/reference-app/app/Pulsar/Services/Internal/Modules/Orders/Jobs/ConfirmOrderJob.php — Actor and tenant reconstruction before Policy authorization.
php
<?php
namespace App\Pulsar\Services\Internal\Modules\Orders\Jobs;
use App\Models\User;
use App\Pulsar\Domain\Orders\Models\Order;
use App\Pulsar\Services\Internal\Modules\Orders\UseCases\ConfirmPendingOrder;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Gate;
final class ConfirmOrderJob implements ShouldQueue
{
use Queueable;
public int $tries = 3;
public function backoff(): array
{
return [10, 30, 60];
}
public function __construct(
public readonly int $orderId,
public readonly int $actorId,
public readonly string $tenantId,
public readonly string $idempotencyKey,
) {}
// #region confirm-order-job
public function handle(ConfirmPendingOrder $useCase): void
{
$actor = User::query()->findOrFail($this->actorId);
$order = Order::query()->findOrFail($this->orderId);
abort_unless($actor->tenant_id === $this->tenantId && $order->tenant_id === $this->tenantId, 403);
Gate::forUser($actor)->authorize('confirm', $order);
$useCase->execute($order->id, $this->idempotencyKey);
}
// #endregion
}Interpretation. Policy denial and tenant mismatch are independent rejection paths.
Verify and troubleshoot
The HTTP and queue tests exercise authorization denials. Do not put authorization only in a Form Request; non-HTTP entrypoints do not have one.