bagisto/bagisto · error · AccessDeniedHttpException

Forbidden

Error message

Forbidden

What it means

Shop API AddressController::update loads the address with findOrFail($request->input('id')) — an unknown id 404s first — then compares $addressToUpdate->customer_id with the authenticated customer's id and aborts(403) on mismatch. It is an ownership guard: the address exists, but it belongs to a different customer than the session's.

Source

Thrown at packages/Webkul/Shop/src/Http/Controllers/API/AddressController.php:83

        Event::dispatch('customer.addresses.create.after', $customerAddress);

        return new JsonResource([
            'data' => new AddressResource($customerAddress),
            'message' => trans('shop::app.customers.account.addresses.index.create-success'),
        ]);
    }

    /**
     * Update address for customer.
     */
    public function update(AddressRequest $request): JsonResource
    {
        $customer = auth()->guard('customer')->user();

        $addressToUpdate = $this->customerAddressRepository->findOrFail($request->input('id'));

        if ($addressToUpdate->customer_id !== $customer->id) {
            abort(403);
        }

        Event::dispatch('customer.addresses.update.before');

        $customerAddress = $this->customerAddressRepository->update(array_merge($request->only([
            'company_name',
            'first_name',
            'last_name',
            'vat_id',
            'address',
            'country',
            'state',
            'city',
            'postcode',
            'phone',
            'default_address',
            'email',
        ]), [

View on GitHub (pinned to 326bc45f17)

Solutions

  1. Refetch the authenticated customer's address list after login/switch and use only those ids.
  2. Clear persisted client state (localStorage address ids) on logout in SPAs.
  3. When merging accounts, re-map address ownership before calling update.
  4. Use the status difference for diagnosis: 404 = id unknown, 403 = id exists but is foreign.

Example fix

// before — stale id from a previous session
put('/api/address/update', { id: 42, ... }) // 42 belongs to another customer → 403

// after — take ids from the current customer's own list
const mine = await get('/api/customer/addresses');
await put('/api/address/update', { id: mine[0].id, ... });
Defensive patterns

Strategy: validation

Validate before calling

$address = $this->customerAddressRepository->find($request->input('id'));
$customerId = auth()->guard('customer')->id();

if (! $address || (int) $address->customer_id !== (int) $customerId) {
    return response()->json(['message' => 'Address not available for this customer'], $address ? 403 : 404);
}

Type guard

function ownsAddress(?object $address, int|string $customerId): bool
{
    return $address !== null && (int) $address->customer_id === (int) $customerId;
}

Try / catch

use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;

try {
    $client->put('/api/address/update', $payload);
} catch (AccessDeniedHttpException $e) {
    // address id belongs to another customer — refetch the session customer's own addresses
}

Prevention

When it happens

Trigger: PUT/PATCH to the shop address-update API where the id belongs to another customer: stale id kept in SPA state after an account switch, request replayed from a different session, or automated clients reusing fixture ids across accounts.

Common situations: Customer logs into a different account in the same browser while the SPA keeps old address ids; QA scripts mixing fixtures from two customers; addresses re-parented after a guest-to-customer merge.

Understand the failure class

Related errors


AI-assisted analysis of bagisto/bagisto@326bc45f17 (2026-08-17). Data as JSON: /api/errors/2b2c1ba8b137bf41. Report an issue: GitHub.