Dynamic User Filtering
Isolate each customer's data using short-lived server-signed embed tokens.
Dynamic filtering scopes shared SQL data to the tenant authenticated by your application. Your server signs a token; an unsigned client_id URL parameter does not authorize access.
Set up data access
- Attach your SQL connector to the embed and configure its permitted tables and tenant-filter column in Data Access.
- Enable Dynamic Filtering in the embed settings.
- Generate an embed signing key. Store the secret only on your server. Copy it immediately; the dashboard never returns it again.
- Set Host website origin to the exact HTTPS origin hosting the iframe, such as
https://portal.example.com. Do not include a path or trailing slash. - Mint a token using the authenticated customer's tenant scope. Never trust a tenant value supplied by the browser without authorization.
Token claims are embed_id, tenant_scope, aud: "formula-bot-embed", iat, exp, and kid. Use HS256 and a maximum lifetime of one hour. Put kid in the claims, matching the generated key ID.
// Your server only. Example using the jose package.
import { SignJWT } from 'jose';
async function tokenForCustomer(authenticatedCustomer) {
const now = Math.floor(Date.now() / 1000);
return new SignJWT({
embed_id: EMBED_ID,
tenant_scope: authenticatedCustomer.tenantId,
kid: EMBED_KEY_ID,
})
.setProtectedHeader({ alg: 'HS256', typ: 'JWT', kid: EMBED_KEY_ID })
.setAudience('formula-bot-embed')
.setIssuedAt(now)
.setExpirationTime(now + 3600)
.sign(new TextEncoder().encode(EMBED_SIGNING_SECRET));
}
Use the token in the iframe's initial src:
<iframe
title="Customer analytics"
src="https://analytics.formulabot.com/embed/YOUR_EMBED_ID?embed_token=SERVER_SIGNED_TOKEN"
width="100%"
height="720"
style="border:0;display:block"
></iframe>
The embed has its own sign-in and explicit Join step. Your host token establishes tenant scope; it does not replace user authentication. An established session cannot change tenant scope by replacing the URL token.
Renew an active session
The iframe requests a fresh token shortly before expiry. Your host should obtain one from an authenticated endpoint on your own server, then return it to that specific iframe. The endpoint must derive the tenant from the authenticated customer and must never return a signing secret.
const EMBED_ID = 'YOUR_EMBED_ID';
const EMBED_ORIGIN = 'https://analytics.formulabot.com';
const frame = document.querySelector('#analytics-frame');
window.addEventListener('message', async (event) => {
if (event.origin !== EMBED_ORIGIN || event.source !== frame.contentWindow) return;
const message = event.data;
if (message?.type !== 'formulabot:renew-request' ||
message.embedId !== EMBED_ID || typeof message.requestId !== 'string') return;
try {
const response = await fetch('/api/customer-analytics-token', {
method: 'POST', credentials: 'same-origin',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ embedId: EMBED_ID }),
});
if (!response.ok) return;
const { token } = await response.json();
if (typeof token !== 'string') return;
frame.contentWindow.postMessage({
type: 'formulabot:renew-token', embedId: EMBED_ID,
requestId: message.requestId, token,
}, EMBED_ORIGIN);
} catch {
// The embed displays an expiry recovery message if renewal cannot complete.
}
});
Give the iframe id="analytics-frame" for this example. Never use "*" as the target origin. If the allowed host origin is absent or renewal fails, the embed asks the user to return to the host for a fresh session. Host code should not send repeated unsigned tenant values or attempt to change tenants inside the same session.
Test before publishing
Use Test with a tenant scope and open the copied link in an incognito window. Test links preserve the test account's files and chats. Reset test data is a separate, destructive action with confirmation. Test accounts are excluded from seat billing.
Verify two different tenants cannot see each other's rows, including exports and generated artifacts. Check expired, missing and invalid tokens and tables missing their configured filtering column. Do not publish until these cases fail safely.
Key rotation retires the previous key with a bounded verification overlap. Update your server to the new key promptly and test renewal; do not rotate merely to view the current key metadata.