Developer track

Step 2 · Mint a token for each user

When a signed-in user loads a page with the copilot, your backend mints a short-lived JWT that says who they are. The two claims that drive isolation are sub (the user) and subdomain (their tenant/workspace). Keep the lifetime short — 10 minutes or less — and give every token a unique jti.

Claim Required Meaning
sub Yes Stable id of the signed-in user. Scopes their data.
subdomain Yes The user's workspace/tenant within your product. Isolation key.
iss Yes Issuer — must match what's registered for your instance.
aud Yes Audience — navi.
exp Yes Expiry. Keep ≤ 10 minutes.
jti Yes Unique token id. The basis of replay protection.
name Optional Display name, for the greeting.

The payload:

json
{
  "sub": "842193",
  "subdomain": "acme-trading",
  "name": "Nurul A.",
  "iss": "your-issuer",
  "aud": "navi",
  "iat": 1721103600,
  "exp": 1721104200,
  "jti": "e7c1…f92"
}

A minimal Node reference (this is what the demo backend does) — sign with your private key, RS256:

js
import { SignJWT, importPKCS8 } from 'jose'
const key = await importPKCS8(privatePem, 'RS256')

async function mintNaviToken(user) {
  const now = Math.floor(Date.now() / 1000)
  return new SignJWT({ subdomain: user.subdomain, name: user.name })
    .setProtectedHeader({ alg: 'RS256' })
    .setSubject(user.id)
    .setIssuer('your-issuer')
    .setAudience('navi')
    .setIssuedAt(now)
    .setExpirationTime(now + 600)   // 10 min
    .setJti(crypto.randomUUID())
    .sign(key)
}
Screenshot — Where minting happens. In your app the user is already authenticated; at page render, your backend mints the NAVI token for that session — the user never sees a second login.
🖼