Authorization

Isolate tenants and bulk actions

Carry trusted tenant context through queries, caches, jobs, and batch operations so one organization cannot affect another.

Multi-tenant software repeats one authorization boundary everywhere. One missing tenant condition can expose many records at once, which is why tenancy bugs produce the worst incident reports.

Carry tenant context everywhere

Derive tenant context from trusted membership, not request data alone. Then include it in storage queries, cache keys, object paths, queues, and logs.

The cache is the classic gap. A cache key such as invoice:193 can leak the same record even when the database query is correct:

// wrong: tenant-blind key, first tenant to cache wins
const cached = await cache.get(`invoice:${id}`)

// right: tenant is part of the key
const cached = await cache.get(`tenant:${tenantId}:invoice:${id}`)

The same applies to object storage paths: exports/acme/2026-08.csv, never a shared exports/2026-08.csv that the next tenant’s download link can reach.

Bulk operations check every item

Apply per-object authorization inside bulk operations instead of checking only the first item. A bulk export may check the first invoice, then load the remaining IDs without tenant filters:

-- every ID is constrained, not just validated up front
SELECT id, total FROM invoices
WHERE id = ANY(?) AND tenant_id = ?

If the query returns fewer rows than the IDs requested, something in the batch was foreign. Decide whether that is a silent skip or a hard failure, and log the event either way.

Background jobs inherit the boundary

Background jobs must carry immutable tenant context from creation through delivery. Letting a worker reconstruct it from user-controlled job data moves the authorization weakness into another process:

{ "job": "export-invoices", "tenantId": "acme", "requestedBy": "usr_8817" }

The worker uses tenantId from the job record the API wrote at enqueue time, never from anything the client sent alongside it.

Mix valid and foreign invoice IDs in one bulk export. Prove the job, cache, stored file, and download path expose no foreign record, including when the foreign ID appears first.

Lesson completed

Take this course offline

Get every free book, course edition, and software download.

Get the download library →