The problem
A shared platform is far cheaper to run than one stack per customer, but a single missing WHERE clause can leak one customer's data to another. Isolation has to hold even when application code has a bug.
Key design decisions
- 01
Tenant identity comes from the token, never the request
Users sign in with Cognito and receive a signed token carrying their tenant ID. API Gateway's authorizer validates it, so a request body or query string can never choose its own tenant.
- 02
Credentials scoped to one tenant
For each request the function assumes an IAM role with the tenant ID as a session tag. Policy conditions then limit those credentials to DynamoDB keys and S3 prefixes belonging to that tenant, so even buggy code cannot reach another tenant's items.
- 03
Row-level security in PostgreSQL
Relational data uses row-level security policies keyed on a tenant setting applied with SET LOCAL inside each transaction, so pooled connections never carry another tenant's context. The application connects as a role that does not own the tables, because owners bypass these policies.
- 04
Per-tenant throttling
API Gateway usage plans with one API key per tenant, or a rate limiter keyed on tenant ID, stop one busy customer from degrading the platform for everyone else.