Object-level authorization decides whether an authenticated actor may perform a specific action on a specific record now. Route access alone cannot answer it: the same endpoint may serve records owned by different organizations or assigned to different reviewers. Derive the actor from a verified session, load or query the target under the permitted scope, and deny when the relationship does not match. Apply the same policy to detail reads, updates, deletes, comments, and bulk item IDs. A random-looking record ID helps avoid casual guessing but is not an authorization rule. Collection endpoints must constrain rows before pagination and aggregation, not filter unauthorized results after counting them.
Object-Level Authorization for Reads and Writes
Working case
Reviewer 29 may view case 47 in organization 6 but not case 62 in organization 9. Both use the same detail URL shape. A client changes the case number in a request, then tries the update endpoint and a bulk export. The server must deny each cross-scope action even if a navigation menu never displayed the other case. A list query for Open cases should include only authorized rows before its count and cursor are calculated. If case 47 is reassigned while the page remains open, the next save must use current authorization rather than the permission observed at page load.
Implementation boundary
function mayReview(actor, record) {
return actor.tenantId === record.tenantId && record.reviewerIds.includes(actor.userId);
}
console.log(mayReview({ tenantId: 6, userId: 29 }, { tenantId: 9, reviewerIds: [29] }));
// Output: falseThe predicate illustrates a policy decision, not a complete endpoint. Run it with server-trusted actor context and current record state, preferably within a data access pattern that cannot first expose the row. For a write, authorize the requested action and validate the record version in the same transaction or an equivalent atomic condition. A bulk request must authorize every item, then define whether unauthorized items reject the whole operation or are reported individually without leaking their contents. Ensure a search index and count endpoint enforce the same scope. Test a valid actor, a same-tenant unassigned actor, a foreign-tenant actor, and a role change.
Cost and boundaries
A policy check can be O(1) when membership and tenant keys are indexed, but a careless per-row authorization lookup turns a list of n records into n extra queries. Push scope into the database query or batch the relation lookup, then verify the query plan under realistic page sizes. Filtering after pagination can yield sparse pages, incorrect counts, and information leaks. Cached policy decisions save work but make revocation slower; use short lifetimes or a permission version when access can change quickly. Measure both denial correctness and query cost.
Failure trace
The API checks only that the caller has a reviewer role, then loads any case by numeric ID. Reviewer 29 requests case 62 and sees a private note from organization 9. Hiding the case in the UI did nothing. Add a server-side object constraint and test direct requests. Another failure filters the first fifty search hits after fetching them; the response contains only three visible hits but reveals that dozens of hidden cases matched. Constrain the index query or authorized result set before ranking, count, and cursor generation.
Verification
- Direct ID changes cannot reveal or mutate a foreign record.
- List counts and cursors derive from authorized rows.
- Revocation between read and write blocks the stale writer.
Practice drill
Create two organizations, two reviewers, and cases 47 and 62. Test detail GET, PATCH, delete, search count, cursor page, and a bulk action with mixed authorized IDs. Reassign case 47 between read and write and ensure the old editor cannot save. Test that a foreign record ID does not reveal its title, status, or whether it exists beyond the chosen response contract. Record database query count for a fifty-row page and ensure permission checks do not create one query per row.
Decision note
Authorize the current action on the current object at the server and constrain collections before counting or paging.
Common Mistakes
- Treating a signed-in reviewer role as permission for every case.
- Relying on unguessable IDs or hidden links as access control.
- Filtering protected rows only after pagination.
Connected lessons
Authorization and Tenant Boundaries; Tenant Scope in Cache and Background Work; Permission Revocation and Active Sessions; Private Export Authorization and Artifact Scope; Search Permissions and Private Results; Conditional Writes and Lost-Update Prevention; Identity and Application Security.
Apply and check
Build Project: tenant-safe case export and review Web Development: authorization and data lifecycle decisions quiz.
