Goal of this module
Get a real app token for an end-user, understand what it carries, and see workspace isolation first-hand by creating a second workspace and confirming it can't see the first's tasks.
Two kinds of user
You and your team are org users — you sign in to API Studio to build. TaskFlow's end-users are app users — the people who call your live /app/{orgCode}/... endpoints. This module is about app users.
No separate registration
App users don't register. The first time someone signs in — by email/password or OAuth — their account and workspace membership are provisioned inline.
1 · Exchange a token
Your app gets an identity token from the provider, then exchanges it for a nostackai app JWT at the token endpoint. That app JWT is what every data call carries.
GET /app/{orgCode}/token
oauthtoken: <provider id token | password-login token>
client_id: <your app client id>
-> 200, header x-nostackai-token: <app JWT> (~48h) The token Lambda validates your client_id against the org record, checks the token issuer against the allowlist, resolves the user's workspace, and signs the app JWT. Grab your client id from API Studio's org settings.
Client id + allowed domains
The app token endpoint is called from your app's frontend, so the org keeps an allowed-domains allowlist and the gateway reflects CORS for matching origins. Add your app's domain (and localhost for local dev) before wiring up a browser client.
2 · What the app JWT carries
- `org` — your org code (the claim is
org, notorg_code). - `tenant_id` / `tenant_code` — the active workspace, once resolved.
- `role` — an app-defined label (e.g.
admin). It is *not* enforced by the gateway (Module 4). - `email`, `fn`, `ln`, `clientid`, `iss` — identity and source.
The role claim grants nothing
The app authorizer ignores the JWT role. Real permissions come only from ACL policies — that's the next module. The role is surfaced purely for your own in-app logic.
3 · Workspaces (multi-tenancy)
TaskFlow uses multi tenancy: each team is an isolated workspace. Entity records live under a partition keyed by org#{orgCode}-{tenantCode}, so one workspace never sees another's data — the runtime scopes every query to the caller's workspace automatically.
POST /app/{orgCode}/user/workspace create a new workspace
POST /app/{orgCode}/user/switch-tenant change the active workspace
GET /app/{orgCode}/user/tenants list my workspaces 4 · Prove isolation
GET /app/{orgCode}/list/task returns the two tasks from Module 2.POST /app/{orgCode}/user/workspace.POST /app/{orgCode}/user/switch-tenant, then get a fresh token.GET /app/{orgCode}/list/task again — it returns empty. Different workspace, different data space.Try it
Run the five steps above. The moment the second workspace returns an empty task list while the first still has its two tasks, you've seen tenant isolation work — no code, no filters you wrote.