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.

http
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, not org_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.

http
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

1With your first user's token, confirm GET /app/{orgCode}/list/task returns the two tasks from Module 2.
2Create a second workspace with POST /app/{orgCode}/user/workspace.
3Switch to it with POST /app/{orgCode}/user/switch-tenant, then get a fresh token.
4Call GET /app/{orgCode}/list/task again — it returns empty. Different workspace, different data space.
5Create a task here; switch back; confirm the first workspace never sees it.

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.

Go deeper