Blog

Five ways to do multi-tenancy with Cognito

7 min read
aws saas multi-tenancy cognito identity

Most of what follows isn’t mine. It comes from Managing B2B identity at scale: Lessons from AWS and Trend Micro (IAM306) at AWS re:Inforce 2022, a joint session between the AWS Cognito team and Trend Micro on their Cloud One platform. I’ve been building multi-tenant systems on AWS for a while and this talk answered several questions I’d been guessing at, so these are my notes on it — the architecture and the numbers are theirs, and I’ve tried to be clear about which parts are their experience and which are my opinion.

The headline: tens of thousands of users across thousands of tenants, one Cognito user pool, no tenant able to see another’s data. What stuck with me wasn’t the end state. It was the five approaches they ruled out first.

SaaS identity is not user identity

The framing that reorganised how I think about this: in a multi-tenant system you need both the user ID and the tenant ID, and together they form the SaaS identity.

That sounds trivial. It isn’t, because most teams treat tenant assignment as an afterthought. The user authenticates, and then somewhere downstream a tenantId gets attached. But where that tenantId comes from, how it’s validated, and whether it’s present on every request — those three questions decide whether you have tenant isolation or just the appearance of it.

Three models

There are three fundamental shapes, each with real trade-offs:

ModelWhat you getWhat it costs
SiloMaximum isolation, per-tenant policy control, own resourcesDeployment complexity, per-tenant capacity planning, every change rolls out N times
PoolEfficient, simple to deploy, elasticShared policies, no per-tenant config, noisy neighbours. Often unacceptable in regulated industries
HybridCentral user identity, tenant assignment held externallyMore work up front, most flexible long-term

Trend Micro went hybrid. For any SaaS product that expects enterprise customers, I think that’s the right default — though I’d note I’ve only run pool and hybrid myself, never a true silo at scale.

The five Cognito approaches

Cognito gives you five concrete ways to implement this. Each maps onto one of the three models.

Custom attribute. Put custom:tenant_id on the user profile. Works as long as a user belongs to exactly one tenant. For B2C with clean tenant assignment it’s usually enough.

Groups. Allows the N:M case — one user in several tenants. The catch is that group IDs land in the JWT. At a handful of groups this is a non-issue. At a thousand, token size becomes the constraint. This is what ruled the approach out for Trend Micro: they described customers wanting access across more than a thousand accounts, with individual users needing all of them.

App client per tenant. Separate OAuth flows per tenant, single user pool. Useful when tenants need different redirect URIs or client configuration.

User pool per tenant. The silo. Full control — own password policies, own MFA settings. The price is that every login needs a lookup (“which user pool does this tenant use?”), and every change ships N times.

External assignment. Trend Micro’s choice: user identity in Cognito, tenant membership in DynamoDB. No token size limit, arbitrary tenants per user, maximum flexibility. You pay for it by building your own token management.

The token exchange

This is the part of the architecture I found most interesting. Rather than carrying tenant context in the Cognito token, they mint their own:

  1. User authenticates against Cognito → Cognito token
  2. User calls the account service → list of available tenants
  3. User picks one → platform token, carrying tenant context and role
  4. Every subsequent API call uses the platform token
const cognitoUser = await validateCognitoToken(req.headers.authorization);

const memberships = await dynamodb
  .query({
    KeyConditionExpression: "userId = :uid",
    ExpressionAttributeValues: { ":uid": cognitoUser.sub },
  })
  .promise();

const platformToken = signToken({
  sub: cognitoUser.sub,
  accountId: selectedAccount.id,
  role: selectedAccount.role,
  // no PII — deliberately omitted
});

Three reasons this is worth the extra moving part:

Tenant switching without re-authentication. The user can exchange for a token scoped to a different tenant at any time. No logout, no second MFA prompt. This was a concrete pain point in their previous system.

PII isolation. The platform token carries no personal data. Downstream services see a user ID, a tenant ID and a role — not a name or an email. The Cognito token, which does carry PII, stays confined to the core identity services. This is the detail I’d steal first.

Decoupling from the identity provider. During migration, users could log in through either the old system or Cognito. Either way the output was the same platform token, so no downstream service had to care which.

Where it hurt

The most useful part of any talk like this is the part where they admit what went badly.

One password policy. One user pool means one password policy for everyone. Their previous system let tenant admins set their own length, rotation and complexity rules. That capability was lost. Fine for most customers; difficult conversations with the few who relied on it.

A nine-month migration. Every user needed a new identity, because the new system uses verified email as the user ID. Cognito’s user migration trigger doesn’t carry MFA seeds across — so there’s no automatic import path for any user with MFA enabled, which is most enterprise users. Their own summary of how that landed with customers was notably blunt, and worth watching for.

SAML federation. Their model — one pool, external tenant assignment — didn’t fit Cognito’s native SAML support. The problem is structural: a federated IdP registered in a user pool can assert identities across the entire pool, and they couldn’t allow one customer’s IdP to claim arbitrary identities system-wide. They ended up building their own SAML handling where each tenant declares which providers it trusts.

What I’d ask before choosing

How do users move between tenants? If users live in more than one, you need token exchange or something like it. Group-based approaches hit token size limits.

Do tenants need their own policies? If yes, you’re looking at a pool per tenant or custom logic in a hybrid setup. If no, a single pool saves you an enormous amount of complexity.

Are you migrating an existing system? Cognito’s migration trigger is excellent right up until your users have MFA.

Do you need federation? Multi-tenant SAML and OIDC is its own project. If enterprise customers will bring their own IdP, budget architecture time for it.

How large does this get? Trend Micro deliberately chose no limits — no cap on tenants per user or users per tenant. That made the architecture harder and means they can absorb any requirement. If you know your limits stay modest, take the simpler option.

For most projects Cognito out of the box is enough. Trend Micro needed more and paid nine months for it. Worth knowing which category you’re in before you start.

Source

AWS re:Inforce 2022, session IAM306: Managing B2B identity at scale: Lessons from AWS and Trend Micro. Worth watching in full — the migration section in particular has detail I’ve compressed here.