Access to a Particular Object

The permission table says that shoppers may read their own carts. Checking that a user has the shopper role is only part of that decision. The server must also check whether the requested cart belongs to that user.

For this example, suppose the shop API stores these two carts and their owners:

Cart Owner
73 Shopper 7
75 Shopper 8

Shopper 7 can read cart 73. Suppose they change the cart identifier in the request to 75:

{
  cart(id: 75) {
    id
    totalCents
    items {
      productId
      quantity
    }
  }
}

If the server checks only the shopper role, it will return shopper 8’s cart. The request is well formed and the caller has the required role. But the permission is to read carts the shopper owns, and the role check says nothing about whether cart 75 is one of them.

Checking access to a particular resource is called object-level authorization. The same kind of check applies to an order or a HopPress post.

Checking ownership

The shop API stores an owner_id on each cart. To authorize a request, the server compares that value with the caller’s identity from the verified credentials. The request supplies only the cart identifier. It does not get to say who the caller is. Creating a cart works the same way. The server records the caller from the verified credentials as the owner, so createCart takes no owner argument.

The server runs this check on every operation on a cart or order, both reads and writes.

Reporting the refusal

When shopper 7 requests cart 75, the server refuses and returns NOT_FOUND, the same code it returns for a cart that does not exist, with "cart": null in the data. In a REST API, the status would be 404 Not Found.

The refusal is not reported as FORBIDDEN, or 403 Forbidden in REST, because that would tell shopper 7 that a cart with identifier 75 exists and belongs to someone else. With NOT_FOUND, a cart that belongs to another shopper looks the same to the caller as a cart that does not exist. This is considered a good practice. The HTTP specification RFC 9110, for example, says a server that wishes to hide the existence of a forbidden resource may respond with 404 instead of 403, and defines 404 as meaning the server either did not find the resource or is not willing to disclose that it exists. GitHub’s API does the same for private repositories, and its documentation says why: to avoid confirming that the repository exists.

Earlier, when a shopper tried to create a product, the server returned FORBIDDEN. In that case the server was enforcing a general rule: only merchants may create products. That rule is public, so the FORBIDDEN response tells the caller nothing about any particular product or shopper.