tenant_id vs sub: where to put tenant context in a JWT

Why conflating tenant identity with user identity in the sub claim is a multi-tenant anti-pattern, and the conventions that work instead.

shield

Private: Decoding, analysis, signature verification, and token generation all run in your browser. Nothing is sent to any server, except a JWKS URL you explicitly enter and click Verify on.

At a glance

Anti-pattern
sub: "tenant_X:user_Y"
Recommended claim for active tenant
tenant_id, org_id, or workspace_id
Recommended claim for memberships
tenants[] or memberships[]
sub purpose (per RFC 7519)
Stable identifier for the user across sessions

A surprisingly common pattern in multi-tenant JWTs is to encode the tenant in the `sub` claim alongside the user id (e.g., `sub: "tenant_acme:user_42"`). It feels efficient — one claim, two pieces of data — but it conflates two unrelated concepts and breaks as soon as a user belongs to more than one tenant. Use a dedicated tenant claim instead.

Why the anti-pattern is tempting

The "compress two ids into one claim" approach has surface appeal. Every claim takes a few bytes, and JWT size matters when tokens go in headers. Conflating sub with the tenant feels like a way to save space and avoid adding a claim.

In practice, the savings are negligible (tokens are typically already 500–2000 bytes; one extra claim adds ~30) and the costs are real. Tooling that consumes JWTs — Sentry user binding, audit logs, analytics — all expect `sub` to be the user. Splitting it across tenants makes everything downstream more complex.

The conventions that actually work

Keep `sub` as the user id, period. Use `tenant_id` (or the provider-specific equivalent — `org_id`, `workspace_id`, `team_id`) for the active tenant. If users can belong to multiple tenants, list memberships in a `tenants[]` array of `{ id, role, permissions }`.

For permissions and roles, use `tenant_role` (or `org_role`) and `tenant_permissions` (or `org_permissions` / `permissions`). All of these claims should be scoped to the active tenant — the JWT represents one session in one tenant, even if the user has access to others.

Frequently asked questions

What's wrong with putting the tenant in sub?expand_more
Two problems. First, `sub` is supposed to be a stable user identifier — your tooling, audit logs, and analytics will treat it as such. Encoding the tenant means the same user looks like different people across tenants, breaking deduplication. Second, when a user joins a second tenant, you can no longer represent them with one `sub` — you have to issue different tokens with different `sub` values. Authentication becomes session-scoped, which is fine, but anything that ties to user history (rate limits per user, recent activity, etc.) breaks.
What if my JWT was already designed this way?expand_more
You can transition by adding a parallel `tenant_id` claim and switching consumers to read from it. The tenant-encoded `sub` becomes a vestigial detail you can phase out once nothing reads it. Keep `sub` as the literal user id going forward.
Should the tenant claim be the tenant id or the slug?expand_more
Use the id (a stable identifier) as the primary claim. Add the slug as a separate claim if you want it for display — slugs can change when a customer renames their workspace, and you don't want every active token to invalidate when that happens.

Related guides

Related Tools