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)
}
🖼