Build
Checking who's looking
Your page gets a token: a short-lived JWT, signed by the deployment that runs the workspace. Your server verifies it, and only then trusts who's asking.
With the SDK
import { fetchKeys, verifyToken } from "@tabs-and-spaces/sdk/server";
const WORKSPACE = "https://chat.acme.com"; // the deployment you serve
const keys = await fetchKeys(WORKSPACE); // cache these
export async function whoIsAsking(request: Request) {
const token = request.headers.get("authorization")?.replace(/^Bearer /, "") ?? "";
const { context } = await verifyToken(token, {
keys,
audience: "https://crm.example.com", // your app's origin
issuer: WORKSPACE,
});
return context; // user, workspace, space, tab, app, locale
}verifyToken() checks that the token:
- is signed with one of
keys; - is meant for your app (its audience is your origin);
- hasn't expired;
- comes from
issuer, if you pass one.
If any check fails, it throws a TokenError. Answer 401.
It uses Web Crypto's Ed25519. That works in Node 22 and later, Deno, Bun, Cloudflare Workers and modern browsers.
Decide which workspaces you serve
If you serve one deployment, keep its address in your configuration. If you serve many (you sell your app to several companies), keep a list. For each request:
- read the token's
isswithout trusting it; - look it up in your list;
- verify with that deployment's keys and
issuer.
Keys
A deployment's public keys are at /api/apps/keys on its address:
{
"keys": [
{
"kty": "OKP",
"crv": "Ed25519",
"x": "jR2Z5Hz…",
"kid": "q3Xr9tL2…",
"alg": "EdDSA",
"use": "sig"
}
]
}They're the same for every workspace on a deployment, and they change only when its owner changes its secret. Cache them. If a token names a key id (kid) you don't have, fetch the keys again, once, before refusing it.
Without the SDK
Any JWT library that supports EdDSA (Ed25519) can verify the token. Check:
algisEdDSA, andkidis one of the deployment's keys;audis your origin;issis a deployment you serve;expis in the future.
See Tokens for every claim.
Tokens are short-lived
A token lasts ten minutes. In your page, call token() before each request rather than keeping one: it returns the current token, or fetches a fresh one near expiry. Don't store tokens, and don't use them as sessions. If your app needs its own session, start it after verifying a token, and keep it short.