Customer Portals & Multi-Tenancy
When building B2B SaaS applications, a common requirement is to display audit logs to your end-customers (e.g., let “John” or “John’s Bakery” see their own logs).
Volidator makes this possible while preserving its Zero-Knowledge model. Your platform can scope logs to a specific tenant, a specific resource target, or a single user. Decryption occurs entirely in the customer’s browser. Volidator’s servers never see who the tenant is, nor what actions they performed.
How It Works
Section titled “How It Works”To support multi-tenant dashboards, Volidator allows you to:
- Tag logs at ingestion time with a
tenantandtarget. - Deterministic Hashing: The SDK hashes these identifiers into
tenantBlindIndexandtargetBlindIndexlocally before transmitting them to Volidator’s API. - Scope Tokens: You generate short-lived, signed JWT tokens specifying the query scope (
tenant,actor,target, orall). - Edge Filtering & Browser Decryption: Volidator queries the database using only the hashed blind indexes and sends the encrypted payloads to the browser, where the embedded widget decrypts and displays them.
Step 1: Log Ingestion
Section titled “Step 1: Log Ingestion”When tracking events, specify the tenant (e.g. organization name/ID) and target (e.g. resource ID/name) in your log() calls. The SDK supports both raw strings and ReferencePayload objects (for JIT Hydration).
// 1. Example: Alice at John's Bakery updates the payment gatewayawait volidator.log({ actor: "alice@johnsbakery.com", action: "gateway.update", target: "payment-gateway-config", tenant: "johnsbakery", // Scopes the log to the organization});
// 2. Example: Bob at John's Bakery logs inawait volidator.log({ actor: "bob@johnsbakery.com", action: "user.login", target: "dashboard", tenant: "johnsbakery",});
// 3. Example: Platform admin modifies Alice's profile (no tenant)await volidator.log({ actor: "admin@platform.com", action: "profile.update", target: "alice@johnsbakery.com",});Step 2: Generate a Scoped Embed Token
Section titled “Step 2: Generate a Scoped Embed Token”In a server-side route of your application, generate the signed embed URL. Specify the identifier parameters (actorId, targetId, tenantId) and a query scope determining what the user is allowed to view.
const { embedUrl } = await volidator.generateEmbedToken({ tenantId: "johnsbakery", // Scopes query to this tenant identifier scope: "tenant", // Filters only logs matching tenantId expiresIn: "30m", // Capped at 1 hour for security});Available Scopes
Section titled “Available Scopes”| Scope | Description | Required Parameters | Matches Logs Where |
|---|---|---|---|
tenant |
Standard B2B multi-tenancy. Returns all logs for a workspace/client. | tenantId |
tenantBlindIndex === HMAC(tenantId) |
actor |
Displays logs of a specific individual user (e.g. “John”). | actorId |
actorBlindIndex === HMAC(actorId) |
target |
Displays logs affecting a specific resource (e.g. “Invoice-99”). | targetId |
targetBlindIndex === HMAC(targetId) |
all |
Combined broad access. | At least one of the above | actor, target OR tenant matches any provided IDs |
Step 3: Embed the Dashboard Widget
Section titled “Step 3: Embed the Dashboard Widget”Pass the generated embedUrl to your frontend and render it inside an <iframe>.
// Next.js (App Router) exampleexport default async function TenantLogsPage() { const { embedUrl } = await volidator.generateEmbedToken({ tenantId: "johnsbakery", scope: "tenant", expiresIn: "30m", });
return ( <div className="logs-container"> <h1>Audit Logs</h1> <iframe src={embedUrl} width="100%" height="600" style={{ border: 'none', borderRadius: '12px' }} title="Audit Logs Portal" /> </div> );}Key Rotation (Zero-Knowledge Keyring Pattern)
Section titled “Key Rotation (Zero-Knowledge Keyring Pattern)”When security guidelines require rotating your client Data Encryption Keys (DEKs), doing so blindly can break decryption and searchability of historical logs (as they were encrypted and hashed under a different key).
To prevent data accessibility loss, Volidator supports a Keyring Model allowing you to retain historical keys for reading while defining a single key for writing new logs.
1. SDK Keyring Configuration
Section titled “1. SDK Keyring Configuration”Instead of a single encryption key string, pass the keyring mapping and specify the activeEncryptionKeyId. This ensures all new logs are encrypted and indexed using the active key version, while past logs remain decryptable.
const volidator = new VolidatorClient({ apiKey: process.env.VOLIDATOR_API_KEY!,
// Specifies which key version to use for new incoming logs activeEncryptionKeyId: "v2",
// List of active and historical keys for decryption & querying keyring: { "v2": "current-encryption-key-entropy...", "v1": "historical-encryption-key-entropy..." }});2. Multi-Key Searching (Under the Hood)
Section titled “2. Multi-Key Searching (Under the Hood)”When generating embed tokens or querying logs, the SDK automatically hashes your search parameter (e.g. tenantId: "johnsbakery") against all keys in your keyring.
Because the SDK sends all derived blind indexes within the secure scoped token, the Volidator API can query logs matching any of these computed blind indexes. This guarantees that your historical logs (encrypted under older keys) are still retrieved alongside newer logs, all without the Volidator platform ever knowing the underlying plaintext values of your keys or query parameters.
Security Best Practices
Section titled “Security Best Practices”- Short Expirations: Embed tokens are processed by the end-user’s browser. To prevent exfiltration or replay attacks, the SDK automatically caps the token’s lifetime (
expiresIn) at a maximum of 1 hour. Keep this value relatively low (e.g.15mto30m) and regenerate the token on each page load. - Restricting Origins: Always pass
hostOriginwhen generating embed tokens. This locks down postMessage communication and JIT hydration handshake to only run on your approved frontend origin (e.g.,https://app.yourdomain.com). - Keyring Limits: Limit your keyring to a maximum of 5 keys. Exceeding this limit will inflate the SQL search parameters and degrade client-side performance. For standard rotation guidelines, a keyring of 5 keys is sufficient to cover rolling histories (e.g. quarterly rotations over a year).