Part B: How to Design Tenant Isolation in a Multi-Tenant SaaS on AWS
Imagine you are building a SaaS application for businesses. Company A uses it to manage customers, invoices and reports. Company B uses the same application for its own business.
Both companies log in to the same website. Your application runs on the same servers, and both customers may use the same database.
That is one of the main benefits of SaaS. You do not need to deploy a completely separate application for every customer.
But there is one rule you cannot compromise on: Company A must never be able to access Company B’s data.
A small mistake in a database query, an API endpoint or a background job could expose information that belongs to another customer. The application might continue working normally while the customer data is being accessed incorrectly.
This is why tenant isolation needs to be part of the architecture from the beginning.
In this article, we will explore how to design tenant isolation for a multi-tenant SaaS application on AWS, how to choose between shared and dedicated resources, and what to check before taking the application into production.
1. What does tenant isolation actually mean?
A tenant is usually a customer organization that uses your SaaS application.
For example, imagine building a logistics management platform. Three businesses subscribe to it:
- NorthStar Logistics manages its deliveries and drivers.
- BlueRoute Transport manages its own delivery operations.
- QuickMove Logistics tracks its shipments and invoices.
All three companies use the same application, but their information belongs to them individually.
Tenant isolation means that each company can access only the data and resources it is authorized to use.
It is not enough to show the correct company name on the dashboard. Isolation must work across the entire application, including APIs, databases, uploaded files, reports, background jobs and administrative functions.
AWS describes tenant isolation as a core consideration in SaaS security. Tenant identity must be carried through the different parts of the application so that access to one customer’s resources cannot accidentally extend to another customer.
Reference: AWS Well-Architected SaaS Lens — Tenant isolation.
2. Start with the request, not the database
One mistake I would avoid is starting tenant isolation only when designing database tables.
The first question should be: how does the application know which customer is making a request?
Consider this API endpoint:
GET /api/shipments/SH-1042
The request asks for a shipment. But knowing the shipment ID is not enough to decide whether the requester may view it.
Shipment SH-1042 might belong to NorthStar Logistics. If a user from BlueRoute discovers that ID, the application must still deny access.
A secure request flow should look something like this:
User sends a request → Authenticate the user → Identify the active tenant → Verify the user's membership and permissions → Access only that tenant's resources → Return the authorized result
Authentication answers, “Who are you?”
Authorization answers, “What are you allowed to access?”
Tenant isolation connects those decisions to the customer’s data. For example, a user might belong to two organizations but have different permissions in each. The application must verify both the selected organization and the user’s membership in it.
Never rely only on a tenant ID supplied by the browser.
A request header such as X-Tenant-ID: northstar can be useful for carrying tenant context, but the server must verify it against the authenticated user’s identity and permissions. Otherwise, a user could change the header and attempt to access another company’s information.
The rule is simple: the client can request a tenant context, but the server must decide whether that context is valid.
3. Choose the right tenant isolation model
Once you know how tenant identity will be established, the next decision is where and how to separate customer resources.
AWS commonly describes three approaches: pooled, silo and bridge. They represent different ways of balancing isolation, cost and operational complexity.
Option 1: Pooled model — share resources
In a pooled model, multiple tenants share the same application infrastructure and often the same database tables.
Each customer’s records are identified by a tenant ID.
For example, an orders table might contain:
| Order ID | Tenant ID | Customer | Amount |
|---|---|---|---|
| ORD-101 | TENANT-A | Acme Retail | 12,000 |
| ORD-102 | TENANT-B | Bright Stores | 8,500 |
| ORD-103 | TENANT-A | Metro Shop | 6,200 |
The records live together, but every query must be restricted to the correct tenant.
For many early-stage SaaS products, this can be an economical and manageable design. You can deploy one application, maintain one shared database and release updates without provisioning a separate stack for every customer.
However, the application must enforce tenant boundaries consistently. A query that forgets the tenant condition can return another customer’s records.
Option 2: Silo model — dedicate resources
In a silo model, each tenant receives dedicated resources, such as a separate database, application deployment or AWS account.
For example:
NorthStar Logistics
-> Application resources A
-> Database A
BlueRoute Transport
-> Application resources B
-> Database B
This provides stronger separation at the resource level and can help meet particular customer, compliance or operational requirements.
The trade-off is that you have more infrastructure to provision, monitor, secure, patch and maintain. Dedicated resources also do not eliminate every risk: shared administrative services, deployment pipelines and support tools still need proper access controls.
I would consider this model when a customer’s contractual requirements, sensitivity of its data or need for dedicated capacity justifies the additional cost.
Option 3: Bridge model — combine both approaches
A bridge model combines shared and dedicated components.
For example, every customer might use the same frontend and application API, while Enterprise customers receive a separate database or dedicated processing resources.
This allows the SaaS provider to offer different levels of isolation without maintaining a completely separate deployment for every tenant.
It can be useful when some customers need stronger separation while others are well served by shared infrastructure.
AWS explains these trade-offs in its guide to SaaS storage partitioning models.
For a new SaaS application, I would generally evaluate the pooled approach first, provided the application’s security requirements support it. I would make tenant identity, access rules and tenant-to-resource mapping independent of the underlying storage arrangement so that dedicated resources can be introduced later where necessary.
That migration still requires careful data movement, access testing and operational planning. It should not be assumed to happen automatically.
4. Designing tenant isolation in the database
The database is one of the most important places to enforce tenant boundaries.
Suppose your SaaS application uses Amazon RDS for PostgreSQL. You have an orders table, and all customers’ orders are stored in it.
A simple design includes a tenant_id column:
CREATE TABLE orders (
order_id UUID PRIMARY KEY,
tenant_id UUID NOT NULL,
customer_name TEXT NOT NULL,
amount NUMERIC(12, 2) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
The tenant_id identifies which organization owns each order.
When an authenticated user requests their orders, the application should scope the query to the tenant established by the server:
SELECT order_id, customer_name, amount, created_at
FROM orders
WHERE tenant_id = $1
ORDER BY created_at DESC;
Here, $1 must be populated from the validated tenant context, not blindly copied from a user-controlled request parameter.
The same principle must apply to updates and deletes. If an API retrieves a record by its ID alone and then modifies it, a guessed or leaked ID could become a way around the intended boundary.
A safer update also checks the tenant:
UPDATE orders
SET amount = $1
WHERE order_id = $2
AND tenant_id = $3;
The application must treat an operation affecting no rows as an unsuccessful operation and respond appropriately.
Do not depend on developers remembering every query
Application-level filtering is important, but it is possible for a developer to introduce a new endpoint and forget the tenant condition.
For PostgreSQL, row-level security (RLS) can provide an additional database-level restriction. A policy can restrict which records a database role is allowed to read or modify.
However, RLS must be configured and tested carefully. Table owners and privileged database roles can have special behavior, and connection pooling requires care when setting per-request tenant context. If the application runs every query with a highly privileged role that bypasses the intended policy, RLS will not provide the protection you expect.
Treat database-level controls as another barrier, not as a reason to ignore application authorization.
For DynamoDB, the equivalent design depends on how items and access patterns are modeled. Tenant identity should be part of the appropriate partition or key design where needed, and the application must enforce the correct tenant scope. A shared table alone does not guarantee isolation.
5. What about uploaded files in Amazon S3?
Tenant data is not limited to database records.
A logistics platform might store invoices, delivery documents and shipment photographs in Amazon S3. Those objects need the same ownership rules as the records in your database.
One possible key structure is:
tenants/TENANT-A/invoices/invoice-101.pdf
tenants/TENANT-A/documents/pod-101.jpg
tenants/TENANT-B/invoices/invoice-201.pdf
tenants/TENANT-B/documents/pod-201.jpg
This naming convention makes ownership easier to understand and manage. However, putting the tenant ID in an S3 object key does not itself enforce access control.
Before returning a download link, the backend must authorize the user for that specific object. If you use presigned URLs, generate them only after validating tenant access, and choose a suitably short expiration period.
Where stronger separation is required, consider additional controls such as scoped IAM permissions, carefully designed bucket policies, S3 access points where appropriate, or separate storage resources for specific tenants.
The right choice depends on the access pattern and isolation requirements. The important point is that the storage layer must not let a user bypass the application’s tenant checks.
6. Tenant isolation must continue beyond the API
This is where otherwise reasonable designs can become unsafe.
You might correctly filter every database query and protect every uploaded file, but still expose customer data somewhere else.
Consider a few examples.
Caching: If your application caches a report under a key such as monthly-report, customers could receive the same cached result. Include the tenant and any other required authorization context in the cache key.
Background jobs: A message placed on Amazon SQS might instruct a worker to generate a report. The worker must use the tenant identity associated with that job and verify its access to the requested records. Do not assume that a background process is safe merely because a trusted service invoked it.
Search and analytics: If tenant records are copied into a search index or analytics store, apply appropriate tenant filters there as well. The original database’s access rules do not automatically protect downstream copies.
Logs and monitoring: Tenant identifiers can help investigate incidents, but logs should not unnecessarily expose customer records, credentials or other sensitive data.
Exports and downloads: Before generating a report or delivering a file, verify that the requesting user has permission to access all the included information.
The broader lesson is that isolation must follow the data through the entire application, not stop at the database.
7. A practical AWS architecture
For a typical SaaS application, a starting architecture might look like this:
Users
|
v
Amazon CloudFront
(if needed)
|
v
API Gateway or ALB
|
v
Authentication layer
|
v
Tenant authorization
|
v
Application services
/ | \
v v v
Amazon RDS Amazon S3 Amazon SQS
(scoped) (protected) (validated jobs)
|
v
Tenant-scoped records
The frontend is shared. The API authenticates users and resolves their tenant context. The application then applies that context when interacting with storage and other services.
CloudFront, API Gateway and the Application Load Balancer serve different purposes, so you would select them based on how the application is deployed rather than add all of them by default.
Likewise, SQS is useful when work needs to be processed asynchronously; it is not required simply because the application is multi-tenant.
AWS Identity and Access Management (IAM) can also constrain access to AWS resources. For suitable designs, attribute-based access control (ABAC) can use identity and resource attributes—such as tenant identifiers—to decide which resources a role may access.
For example, an application role could receive permission to access resources associated with a particular tenant. IAM-based restrictions complement application-level authorization; they do not replace checks on ordinary database queries or business operations.
AWS provides an implementation walkthrough in How to implement SaaS tenant isolation with ABAC and AWS IAM.
8. How should you test tenant isolation?
Do not wait for a security review to discover that two customers can see each other’s records.
Create at least two test tenants and verify the boundary deliberately.
For example, create NorthStar Logistics and BlueRoute Transport, each with its own users, orders and uploaded documents.
Then test scenarios such as:
- NorthStar users can read and update their own orders.
- NorthStar users cannot read or update BlueRoute orders, even when they know the order ID.
- Changing a tenant ID in a request does not grant access to another tenant.
- File-download links cannot be generated for another tenant’s files without authorization.
- Cached reports and search results remain scoped to the correct tenant.
- Background jobs cannot process another tenant’s data through a manipulated or incorrectly assigned job payload.
- Administrative support access is restricted, authorized and recorded appropriately.
Test both successful and unsuccessful operations. For example, an unauthorized update must not silently modify a record even if the API response does not reveal whether the record exists.
These tests should run automatically where practical, particularly around authorization and data-access changes. A shared SaaS platform can have many endpoints, and every new feature introduces another opportunity to cross a tenant boundary accidentally.
9. What I would choose for a new SaaS product
I would not start by deploying a separate AWS account, database and application for every customer unless the product’s requirements justified it.
For many early SaaS products, a pooled design can keep infrastructure manageable while supporting multiple customers. But I would establish the boundaries early:
- Every customer organization has a stable tenant identity.
- User membership and permissions are validated on the server.
- Database queries and data changes are scoped to the authorized tenant.
- Files, cached results, background jobs and analytics follow the same rules.
- Automated tests attempt to access data across tenant boundaries.
- Monitoring and operational tools are designed with tenant ownership in mind.
I would also keep the mapping between a tenant and its resources separate from ordinary application logic. If a customer later needs a dedicated database or isolated processing environment, that mapping can help route requests to the right resources without rewriting the whole product. The move itself still needs a planned migration and security verification.
A SaaS subscription tier should not automatically dictate the security boundary. A customer might pay for additional features without needing dedicated infrastructure, while another customer may have contractual or regulatory requirements that justify stronger isolation.
Make that decision based on risk, customer needs, cost and operational capability.
Conclusion
Tenant isolation is one of the fundamental decisions in multi-tenant SaaS architecture. It determines how customer identities, database records, files and application services are separated while allowing the product to operate as a shared platform.
AWS gives you several ways to build these boundaries, from shared resources with tenant-aware access controls to dedicated resources for customers who need stronger separation.
The key is not to choose the most complicated architecture. It is to make customer ownership explicit, enforce it at every relevant access point and test that one tenant cannot cross into another tenant’s data.
A well-designed SaaS platform should make the safe path the normal path. Developers should not have to remember a special security rule for every endpoint; the application’s structure, permissions and tests should consistently reinforce the same tenant boundary.

