Sign Visitors In From Your Site

Let your server sign users into an embed with a short-lived token, with no sign-up form and no email code.

If your app already signs its users in, the embed can trust that. Your server signs a short-lived token that names the user, and the embed signs that person in and joins them automatically. They never see a sign-up form or an email code.

Turn it on

  1. Open the embed. Under Access & security, set Who can join to Your site signs visitors in automatically.
  2. Click Generate / rotate to create a signing key. Copy the secret straight away, because it is shown only once. Store it on your server, never in browser code.
  3. Add your site under Allowed websites, for example https://app.example.com. This stops other sites from showing the embed, and it lets the embed ask your page to renew a session.

Sign a token on your server

The token is an HS256 JWT signed with your embed's secret.

ClaimRequiredValue
embed_idYesYour embed's ID
kidYesThe signing key ID (also put it in the JWT header)
audYesformula-bot-embed
iat, expYesIssue and expiry times in seconds. At most one hour apart.
tenant_scopeYesThe customer or account the user belongs to. It filters data when dynamic filtering is on.
subYesThe user's stable ID in your system
emailYesThe user's email, shown in your Users list
nameNoThe user's name, shown in the embed and in Users
jtiYesA unique random ID for each token
// Your server only. Example using the jose package.
import { randomUUID } from 'node:crypto';
import { SignJWT } from 'jose';

export async function embedTokenFor(user) {
  const now = Math.floor(Date.now() / 1000);
  return new SignJWT({
    embed_id: EMBED_ID,
    kid: EMBED_KEY_ID,
    tenant_scope: user.accountId,
    email: user.email,
    name: user.name,
  })
    .setProtectedHeader({ alg: 'HS256', typ: 'JWT', kid: EMBED_KEY_ID })
    .setAudience('formula-bot-embed')
    .setSubject(String(user.id))
    .setJti(randomUUID())
    .setIssuedAt(now)
    .setExpirationTime(now + 3600)
    .sign(new TextEncoder().encode(process.env.EMBED_SIGNING_SECRET));
}

Generate a new token each time you render the page, and put it in the iframe's src:

<iframe
  title="Analytics"
  src="https://app.betteranalyst.com/embed/YOUR_EMBED_ID?embed_token=SERVER_SIGNED_TOKEN"
  width="100%"
  height="720"
  style="border:0;display:block"
></iframe>

How visitors are identified

  • Each sub is a separate visitor, scoped to this embed. The same sub in two different embeds is two different visitors.
  • The email in the token is only for display. It never signs anyone into an existing Better Analyst account, and visitors can't sign in any other way.
  • Each token signs a browser in once, so a copied iframe link can't open a second session. Reloading the iframe in the same browser keeps working.
  • If a different sub arrives in the same browser, the previous visitor is signed out and the new one is signed in.
  • A visitor's tenant_scope is fixed the first time they sign in. If one user can belong to several customers, give each customer and user pair its own sub, such as acme:42.

Sessions, renewal and revoking access

A visitor's session lasts until their token's exp, at most one hour. To keep people signed in longer, either reload the iframe with a fresh token, or answer the embed's renewal request with a new token for the same user (see Renew an active session). A renewal token for a different sub is refused.

To revoke someone's access, stop issuing tokens for them. Their access ends when their current token expires. To cut access off immediately, Block them in Users. Remove frees their seat, but they rejoin the next time your site signs them in. Rotating the signing key ends every session within an hour, once the old key stops being accepted.

Seats

Each visitor your site signs in takes a billable seat, the same as a visitor who signs up, and counts toward your Seat cap. Once the cap is reached, new visitors see a "workspace is full" message.