Object ID checks stop a signed-in user from reading or changing a record they do not own. A valid session proves identity. It does not prove the caller may touch the row, file, or job named in the URL.

Teams that skip this check ship a classic insecure direct object reference. The attacker does not need a stolen password. They change an integer, a UUID, or a slug and the API returns someone else’s invoice, ticket, or upload. That is an authorization bug, not a rate-limit problem and not a webhook problem.

What object checks protect

These checks sit on every path that takes an identifier from the client. That includes GET, write, delete, export, and share endpoints. The same rule applies to background jobs that accept an ID from a queue message the client originally created.

Protect these surfaces first:

If the identifier is guessable, the bug is easier to find. Unpredictable IDs help, but they are not a substitute for an ownership check. UUIDs leak in logs, referrer headers, and support screenshots.

Design choices that matter

Treat object authorization as product behavior. Write the rule next to the handler, not in a forgotten middleware comment.

Prefer a small helper the whole service uses, such as load_invoice_for(caller, id). Copy-pasted SQL in each handler drifts. One helper that always filters by tenant_id is easier to review and easier to test.

Common mistakes

A few patterns show up in almost every incident write-up.

Safe rollout

Start by listing every route that accepts an object ID. Map each one to an owner column and an action. Add a test that uses two tenants and expects a not-found on the cross-tenant ID. Run that suite in CI before you change production behavior.

Roll the check out endpoint by endpoint. Log would-be denials for a short window if you are unsure about a shared-object path, then enforce. Give support a documented impersonation flow with its own audit trail, not a bypass flag.

Alert when a single token produces a spike of not-found responses on ID routes. That pattern is closer to probing than to a confused client.

What to tell partners

Publish that clients must not assume an ID they can see is an ID they may call. Say that unknown IDs return not-found, and that bulk endpoints reject the whole batch if any ID falls outside the caller scope, or skip those rows and return a per-item status. Pick one and document it.

Object ID checks belong next to authentication, not after it as a polish item. Ship the helper, cover it with a two-tenant test, and treat a cross-tenant hit as a defect worth a postmortem.