Multitenant SaaS design questions are easy to answer badly.
A weak answer says, "Add tenant_id everywhere and encrypt the database." A better answer explains where tenant isolation is enforced, how authorization works across each access path, how background jobs carry tenant context, and how you stop one customer from hurting another customer's availability.
This topic comes up often in system design interviews, especially for enterprise products with sensitive data. The original PracHub concept write-up on secure multitenant SaaS architecture frames it around legal workflows, where documents, matters, privileged communications, and audit trails all raise the bar.
Let's turn that into a practical interview-ready design.
What the interviewer is really testing
The interviewer wants to know if you can design a SaaS system where many customers share infrastructure without sharing:
- Data
- Permissions
- Search results
- Cached responses
- Background jobs
- Operational blast radius
- Observability access
That last point matters more than many candidates expect. Multitenancy is not just a database problem. It touches authentication, authorization, queues, object storage, search indexes, logs, metrics, admin tools, and incident response.
A good answer should show that you understand tradeoffs. Shared infrastructure is cheaper and simpler early on. Dedicated infrastructure gives stronger isolation for large or regulated customers, but it adds migration, deployment, cost, and operations work.
Pick the tenancy model first
Most SaaS systems use one of three models:
- Shared database, shared schema
Every tenant's data lives in the same tables. Tenant-owned tables include a tenant_id.
This is cost-effective and simpler to operate, but it depends on correct tenant scoping everywhere.
- Shared database, separate schema
Each tenant has its own schema inside the same database.
This gives a stronger boundary than shared tables, but migrations and schema management become harder.
- Separate database per tenant
Each tenant has its own database.
This improves isolation and noisy-neighbor control, but connection management, migrations, backups, and provisioning are more complex.
For most interview answers, start with shared services and a shared Postgres database with strong logical isolation. Then mention that large or regulated tenants can move to a dedicated database, bucket, or deployment tier.
That tradeoff sounds realistic. It avoids pretending every customer gets fully dedicated infrastructure from day one.
Tenant isolation must exist at multiple layers
Do not rely on one WHERE tenant_id = ? check and call it done.
A serious design applies tenant scoping across the system:
- API authentication
- Authorization middleware
- Database predicates
-
Postgresrow-level security - Object storage key prefixes
- Signed URL generation
- Search and vector index filters
- Cache key prefixes
- Queue routing
- Worker pools
- Audit logs
- Admin tools
- Observability permissions
For example, a document might be stored in S3 under:
tenant/{tenant_id}/matter/{matter_id}/doc/{doc_id}
That naming pattern helps, but naming alone is not security. Your service should authorize the request before it generates a signed URL. The signed URL should have a short TTL. Metadata should be checked before bytes are served.
Authentication and authorization are different
Authentication answers: "Who are you?"
Authorization answers: "What can you access?"
Enterprise SaaS often supports SAML or OIDC single sign-on. The system maps identity-provider groups into application roles. Session tokens or JWTs may contain claims such as:
{
"sub": "user_123",
"org_id": "tenant_456",
"roles": ["admin"],
"exp": 1760000000
}
Roles are useful, but roles alone are usually too coarse.
RBAC works for broad permissions like:
adminmemberviewer
Legal-style workflows often need ABAC too. Access may depend on:
tenant_idmatter_iddocument_classificationjurisdictionethical_wall_group
A common pattern is to combine them. Roles grant capabilities. Attributes constrain which resources those capabilities apply to.
For example, a user may have permission to view documents, but only for matters they belong to, and only if the document is not blocked by an ethical wall.
Put authorization near every resource access
Route-level checks are not enough.
You need a common authorization API, something like:
authorize(actor, action, resource)
That call should happen before sensitive resource access, whether the caller is:
- A REST endpoint
- A GraphQL resolver
- A background worker
- A CSV export job
- A search endpoint
- A document preview service
- An admin impersonation tool
Policy can live in application code or a system like Open Policy Agent. The key idea is consistency. Every path that reads or writes tenant data needs a policy decision.
This is where many designs fail. The main API may be scoped correctly, while a batch export, preview endpoint, webhook retry, or support tool bypasses the same checks.
Design the database so unsafe queries are harder to write
In a shared-schema model, every tenant-owned table should include tenant_id.
Example tables:
documents (
tenant_id,
matter_id,
document_id,
title,
created_at
)
matters (
tenant_id,
matter_id,
name,
created_at
)
Indexes should match tenant-scoped access patterns:
CREATE INDEX documents_tenant_matter_created_idx
ON documents (tenant_id, matter_id, created_at);
Business identifiers should usually be unique within a tenant, not globally:
UNIQUE (tenant_id, external_id)
Postgres row-level security can backstop application mistakes:
tenant_id = current_setting('app.tenant_id')
RLS is not a replacement for clean application design, but it can reduce the damage from an unscoped query.
Treat search and vector retrieval as high-risk paths
Search is a common leak point.
If you use OpenSearch, Elasticsearch, pgvector, or a vector database, every query must include tenant and permission filters before results are returned.
For sensitive documents, retrieval should filter by:
tenant_idmatter_id- User-accessible document IDs
Do not fetch the top-k chunks globally and then filter afterward. That can leak through ranking, snippets, timing, logs, or accidental response fields. The permission boundary needs to be part of retrieval, not a cleanup step after retrieval.
Derived data needs the same treatment. OCR text, embeddings, summaries, previews, and cached snippets are still tenant data.
Background jobs need explicit tenant context
Asynchronous processing is another common source of bugs.
Suppose a user uploads a legal document. The system creates jobs for virus scanning, OCR, embedding generation, indexing, and preview creation.
Each job should carry tenant context explicitly:
{
"tenant_id": "tenant_456",
"matter_id": "matter_789",
"document_id": "doc_123",
"requested_by": "user_999"
}
The system should validate permissions when the job is enqueued and again when it runs. That second check matters because permissions can change while a job is waiting.
Derived artifacts should be written back into tenant-scoped stores. Queue names, routing keys, and worker pools may also be partitioned or rate-limited by tenant.
Encryption helps, but it does not fix bad authorization
Encryption belongs in the design:
-
TLSfor data in transit - Storage encryption for data at rest
- Optional per-tenant keys through
AWS KMS,GCP KMS, orHashiCorp Vault
Per-tenant envelope encryption can support tenant-specific key rotation or deletion. It also adds latency, key-management paths, and new failure modes.
The interview mistake is to over-index on encryption. Encryption protects against storage compromise. It does not stop an authenticated user from reading the wrong matter if authorization is broken.
Access-control correctness is the bigger application-layer risk.
Audit logs should answer who did what
Enterprise systems need audit trails for security and compliance.
Log events such as:
- Login
- SSO group sync
- Permission changes
- Document upload
- Document download
- Search
- Export
- Admin impersonation
- Failed authorization
A useful audit event includes:
{
"actor_id": "user_999",
"tenant_id": "tenant_456",
"resource_id": "doc_123",
"action": "document.download",
"decision": "allowed",
"ip": "203.0.113.10",
"user_agent": "Mozilla/5.0",
"timestamp": "2026-01-15T12:00:00Z"
}
The audit log should avoid document contents, prompts, secrets, and access tokens.
There is also a design tradeoff. Synchronous audit writes give stronger confidence but add latency and failure coupling. Asynchronous audit writes improve availability but need durable queues and retries. A strong answer proposes a hybrid: block on authorization, emit audit events to a durable append-only stream, and monitor for delayed or missing audit records.
Protect tenants from noisy neighbors
Security includes availability.
One tenant's large import should not exhaust all background workers or saturate shared database IOPS for everyone else.
Use:
- Per-tenant rate limits
- Quotas
- Queue partitioning
- Worker-pool isolation
- Query timeouts
- Resource monitoring by tenant
This is where shared infrastructure needs careful guardrails. Tenant isolation includes performance failure boundaries, not just data leakage.
A strong interview answer structure
If asked to design a secure multitenant document management system, structure the answer like this:
- Clarify requirements
Ask whether tenants are companies or law firms, whether users can belong to multiple tenants, whether documents are scoped to matters, and whether the system needs SSO, audit logs, data residency, or dedicated infrastructure.
- State assumptions
Use shared application services, shared Postgres for metadata, object storage for files, and strict logical isolation by tenant_id and matter_id.
- Cover identity and access
Use OIDC or SAML, map groups to roles, combine RBAC and ABAC, and call authorize(actor, action, resource) across all access paths.
- Cover data isolation
Put tenant_id on tenant-owned tables, use scoped indexes, enable RLS on sensitive tables, namespace object keys, and generate signed URLs only after authorization.
- Cover operations
Add audit logs, per-tenant rate limits, encrypted storage, key management, queue partitioning, and guardrails for admin access.
- Call out tradeoffs
Shared database is simpler and cheaper for most tenants. Dedicated databases or buckets may be needed for large or regulated customers.
If you want more interview prompts around this style of system design, PracHub has a broader set of technical interview questions that pair well with this topic.
Common mistakes to avoid
The biggest mistake is treating tenant_id like a UI filter. It is a security boundary.
Another mistake is checking authorization only at the route layer. Secondary paths often cause leaks: exports, previews, search snippets, OCR jobs, embeddings, cached responses, webhook retries, and support tooling.
A third mistake is using encryption as a substitute for authorization. You need both, but they solve different problems.
If you can explain those risks clearly, your answer will sound much closer to production engineering than checklist security. For a compact version of the concept, use the PracHub guide to secure multitenant SaaS architecture as a review sheet before practicing the full system design.
Top comments (0)