Free & open · no paywall, ever

IntegrAuth Academy

Identity, security and practical AI — free, from zero. Byte-sized lessons, real diagrams, hands-on labs, zero fluff. Two programs, each with its own exam and certificate.

20 tracks 241 lessons 242 hands-on labs 230+ diagrams 100% free

English is hard? Your browser can translate this page into Tamil, Telugu, Kannada, Malayalam or Hindi. Tech words stay in English so you learn the real names.

How?
  • Chrome on phone: ⋮ menu → Translate
  • Chrome on computer: right-click → Translate to …
  • Edge: the translate icon in the address bar
Identity & API Security Track 4 · Authorization
0/166 lessons read
AUTHORIZATION · LESSON 4

Multi-tenancy and tenant isolation

Most business apps serve many customer companies from one system. So every permission check needs one more question: whose data is this? Your goal: ask "which tenant?" on every request, so Maya never reads a rival company's invoices.

Key points
  • A tenant is one customer company in a shared product.
  • Take the tenant from the verified token, never from the URL or body.
  • Put the tenant filter on every query. Add row-level security as a backstop.
  • A tenant admin has power only inside their own tenant.
  • Test with two tenants on every release.

What is a tenant?

A tenant is one customer company inside your shared product. It has its own people, roles and data. When many tenants share one system, we call it multi-tenancy. The wall between tenants is made of your code.

Think of it like…

one apartment building with many flats. All families share the roof. Each family has a locked door. A permission check is that lock.

Where the tenant comes from

The tenant must come from something the caller cannot change. That is the verified token or the server-side session. The sign-in system puts a tenant claim in the token, such as tenant: "acme". Your API checks the signature first, then reads the tenant.

Never trust a tenant from a URL or request body. The caller types those. Maya could write ?tenant=globex in one second. If they disagree with the token, reject the call.

Maya's signed tokentenant: acmeAPItenant from the tokeninvoices table101 · acme ✓ seen102 · acme ✓ seen205 · globex ✗ hiddenGET /invoices?tenant=globexrejected: the token says acmeWHERE tenant_id= 'acme'Tenant: token → API → filter → database. Never from the URL.
Figure — The tenant travels with the request. The tenant comes from the signed token, never from the URL. The filter keeps only Acme's rows. A request that names Globex in the URL is rejected.

The filter on every query

In a shared database, all tenants' rows sit in one table. A tenant_id column tells them apart. Every read and every write must use it. This is the tenant filter. The same holds for permission data (see Go deeper).

Cross-tenant BOLA

You will meet BOLA (broken object level authorization) in the OWASP tour. Here it crosses the tenant line, so it is cross-tenant access. The code looks up an invoice by id only, with no tenant filter.

Story time

Maya works at Acme. Her invoice link ends in 101. She tries /invoices/205. Her token is valid, so the code returns the invoice. It belongs to Globex. No check asked whose invoice it was.

Row-level security: the database backstop

Row-level security (RLS) lets the database apply the filter itself. The app sets the current tenant once per request, from the token. Then a query that forgets the filter still returns only Acme's rows. RLS is a second layer. If you set the tenant from the URL, RLS protects nothing. The PostgreSQL lines are in Go deeper.

Two kinds of admin

A tenant admin manages the people and settings of one tenant. The role counts only inside that tenant. Check the role together with the tenant, as in B2B organizations. A platform admin works for you, the product company, and sits outside every tenant. That power is dangerous. Support staff should reach a tenant's data only through a short grant that the tenant approves, with a log. Never give a permanent "see all" role.

Watch out

If a token has no tenant claim, do not fall back to "all tenants". Refuse the call. This is fail-closed.

Test for leaks

Write a test with two tenants. Sign in as an Acme user. Ask for Globex objects by id, by list and by write. Every call must fail. Run it on every release.

Why it matters

One missing "which tenant?" can expose every customer at once. Use layers, called defense in depth. The token gives the tenant, the filter applies it, the database enforces it and tests prove it. The gateway cannot do this alone. It does not know who owns an invoice.

Go deeper: RLS in PostgreSQL, pitfalls, and per-tenant keys and login

Here is row-level security in PostgreSQL (version 18 documentation):

PostgreSQL lineWhat it says
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;From now on, every query on this table must pass a policy.
CREATE POLICY tenant_only ON invoices USING (tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::uuid);Show only rows of the current tenant. If no tenant is set, the value is NULL and no rows match.
SELECT set_config('app.tenant_id', 'a1b2…', true);The app sets the tenant for this transaction only.

PostgreSQL's documentation says superusers and roles with the BYPASSRLS attribute always skip row security. Table owners usually skip it too, unless you run ALTER TABLE … FORCE ROW LEVEL SECURITY. So the app should connect as a plain role that owns nothing. If a table has RLS on and no policy, nobody sees any rows. Set the tenant with set_config(…, true) inside the transaction. Then a pooled connection cannot carry one tenant's value into the next request. The NULLIF matters: on a reused connection the setting can be an empty string, and an empty string cannot become a uuid.

The tenant belongs in permission data too. In an OpenFGA style graph, put the organization in the tuples. For example, (org:acme, org, document:report) says the report belongs to Acme (see permissions as a graph). In a policy engine, put the tenant in the rule input (see policy as code).

Some tenants want their own login provider, their own encryption keys, or data in one country. A shared design makes this harder. See B2B organizations for per-tenant login, and multi-tenancy in architecture for silo, pool and bridge designs.

🧪 Interactive lab — enable JavaScript to play with this one.

🧪 Try it free

Does your gateway policy carry the tenant claim? Check it with Gateway JWT Lint. Or talk to our team about a multi-tenant authorization review.