An explicit tenant boundary.
Fresh memberships, resource-tenant checks and a small audit record with fixed fields.
Create a tenant and manage membership
Authenticated application code supplies the actor identity. Tenants::create makes that user an owner. Only a current tenant owner can add, change or remove members. Ownership changes are serialized and cannot remove the final owner. Global admin roles do not grant tenant membership automatically.
use Icom\Auth\Tenants;
$tenantId = $tenants->create($authenticatedUser, 'Workspace');
$tenants->setMember($authenticatedUser, $tenantId, $memberId, 'editor');
$tenants->setMember($authenticatedUser, $tenantId, $memberId, null); // revokeAuthorize the actual resource
A tenant ID in a route or request is a selector, never proof of access. Fetch the resource with a tenant-qualified query, then pass its stored tenant_id to authorize(). The helper checks that it equals the requested tenant, that the account remains active and that fresh membership grants the ability. Configure additional tenant roles through a container factory if needed.
$note = $db->table('tenant_notes')
->where('tenant_id', $tenantId)->where('id', $noteId)->first();
if (!$note) throw new \Icom\Http\HttpException(404, 'Note not found.');
$tenants->authorize($user, $tenantId, 'notes.update', $note['tenant_id']);
// Keep the same tenant filter on the subsequent write.No automatic query scoping
The example tenant_notes table belongs to your application; it is not the demonstration notes schema. The helper does not silently rewrite SQL, isolate physical databases, derive trusted tenants from headers or replace object-level ownership policies. Membership can change while a request is in flight; operations requiring a strict transaction-time authorization guarantee must lock and recheck membership within the write transaction.
Read security audit events
Account creation/role/password/disable changes, session issuance/logout, factor enrollment/verification/recovery and reset operations write fixed-field audit records. Tenant membership events include the tenant ID. Records contain event, outcome, timestamps and IDs; no password, OTP, secret, token, email, IP or request body. Known-account password failures and factor failures are recorded; the audit is not a complete traffic log.
php bin/icom audit:recent all 50
php bin/icom audit:recent ACCOUNT_ID 50
// Trusted application event; no arbitrary metadata payload:
$audit->record('invoice.approved', $user->id, $user->id, tenant: $tenantId);Retention and trust
The application Audit API appends and reads; it has no edit/delete endpoint. The database operator still has power to change rows, so this is not a tamper-proof ledger or independently retained evidence. Define retention, export, access control and alerts for your application. No public administrative console, external audit collector or automatic retention deletion is enabled.