Ask
63

is putting org_id in the jwt normal for multi-tenant or is that how you leak data Multi-Tenant

First multi-tenant thing I have built. Every row has an org_id and every query filters on it.

The convenient thing is to put org_id in the token so the server does not look up membership on every request. Half of what I read says that is standard practice and half says never put authorisation data in a token. Both sides sound confident.

What do people who have actually shipped this do?

9 answers Share
Report

Answering anonymously — a moderator will review it first.

  • @hemline_hana · 3w ago · 3 replies

    Normal and safe, with two conditions.

    One: the claim is minted by you after checking membership, and is never taken from anywhere else. The leak in these systems almost never comes from the claim being in the token. It comes from one endpoint that reads req.body.orgId or an X-Org-Id header because it was easier that afternoon, and now a valid user of org A can address org B by editing a request.

    Two: there is exactly one function in your codebase that produces the org id used by queries, and it reads the verified token and nothing else. Everything goes through it. Then "can this leak" is a question about one function rather than about 200 handlers.

    And write the test. A token for org A, a request for a resource in org B, assert 404 (not 403 - do not confirm the resource exists). Ten lines, catches the entire class, and almost nobody writes it.

    97
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @thea_mandel · 3w ago

      The test is the part people skip and it is the part that would have caught every multi-tenant leak I have read a postmortem about. Parameterise it over your resource types and it stays useful as the app grows.

      32
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
    • @burr_bennet · 3w ago

      If you are on Postgres you can go one step further and set the org id as a session variable that row level security policies read. Then a handler that forgets its WHERE clause returns nothing instead of everything, which is a much better failure mode to have as your default.

      13
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @rueben_alsop · 3w ago

    Remember the claim outlives the membership. Remove someone from an org and their existing token still says they belong until it expires. Short TTL, or re-check membership on writes, or both. This is the same revocation conversation as bans, just with a different noun.

    38
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @p99_hana · 3w ago · 2 replies

    The operational problem with org_id in the token is switching orgs, and it is worth deciding now rather than in six months.

    Either you re-mint the token on switch, which is one extra round trip and is completely fine, or you put all memberships in the token and choose at query time, which is also fine until someone belongs to 200 orgs and your cookie no longer fits in a header. Pick the first one unless you have a specific reason.

    54
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @stack_trace_sy · 3w ago

      We found the header limit at around 300 memberships and it presented as a 431 from one proxy in one region, so it looked like an infrastructure problem for most of a day.

      19
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @burr_and_edge · 3w ago

    Middle ground I like: trust the claim for reads, re-read from the database for billing changes, member removal and anything an admin does. That is a small set of operations where being ten minutes stale is genuinely expensive, and it costs you nothing on the 99% of traffic that is just reading a list.

    16
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @labcoat_lior · 3w ago · 2 replies

    Do not put it in the token at all. Tokens are for identity, authorisation belongs in the database, look it up per request.

    16
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @hemline_hana · 3w ago

      That is a defensible engineering position and it is not a rule. "Tokens are for identity only" is a preference that gets repeated as though it were in a specification somewhere. Signed authorisation claims are extremely normal and the entire industry runs on them; the discipline is about TTL and about having one place that reads the claim, not about purity.

      If you look up membership per request you are back to a database read per request, which is a fine choice, but be honest that you chose it.

      13
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report