Tenant isolation means that a valid user from one customer cannot read or change another customer’s records. Authentication and role checks are necessary, but they are not sufficient: every data-access path must also carry trusted tenant context.
Last updated: September 8, 2026.
$sql = 'SELECT project_id, name
FROM projects
WHERE tenant_id = :tenant_id
AND project_id = :project_id';
$stmt = $pdo->prepare($sql);
$stmt->execute([
'tenant_id' => $sessionTenantId,
'project_id' => $requestedProjectId,
]);
$project = $stmt->fetch();The tenant ID comes from the authenticated server-side session or a verified token—not from a hidden field, query string, or request body. The requested object ID is never used alone.
Apply tenant scope everywhere
Use the same rule for SELECT, UPDATE, and DELETE. For an update, include both identifiers in the predicate and confirm that exactly one row was affected. Inserts should take tenant_id from trusted context even when the client submits a tenant value.
Centralize this behavior in repositories, query builders, or database policies. Relying on every developer to remember a filter is fragile. Row-level security can provide another enforcement layer when the database supports it, but the application must still establish the correct tenant context.
Protect indirect access paths
Files, cache keys, search indexes, exports, background jobs, and API calls need the same isolation. Prefix cache and storage keys with the tenant identifier. Include tenant context in queued work, then revalidate ownership when a worker loads the target record.
Test for cross-tenant access
Create two tenants in automated tests. Authenticate as tenant A and attempt to read, update, delete, export, and enqueue work for tenant B’s known record IDs. Every path should return a neutral not-found or forbidden response without revealing whether the object exists.
AWS’s tenant-isolation guidance emphasizes that an authenticated and authorized user can still cross tenant boundaries unless resource access is explicitly scoped.