Multi-Tenant SaaS on Azure: Architecture, Identity, and Data Isolation

Multi-Tenant SaaS on Azure: Architecture, Identity, and Data Isolation

Introduction

[Explain business drivers: serve multiple customers from shared infrastructure while ensuring security, compliance, and cost efficiency.]

Multi-Tenancy Models

Model Isolation Level Cost Efficiency Complexity
Silo (Dedicated) High Low Low
Pool (Shared) Low High Medium
Hybrid Medium Medium High

Reference Architecture

flowchart TB subgraph Internet U[Customers] end subgraph Azure Front Door AFD[Global Entry + WAF] end subgraph App Tier APIM[API Management] CA[Container Apps / AKS] end subgraph Identity ENTRAID[Entra ID B2C] end subgraph Data Tier COSMOSDB[(Cosmos DB\nPartition Key: TenantId)] SQLPOOL[(Azure SQL\nRow-Level Security)] end U -->|HTTPS| AFD AFD --> APIM APIM --> CA CA --> ENTRAID CA --> COSMOSDB CA --> SQLPOOL

Step-by-Step Guide

Step 1: Tenant Onboarding

[Provision tenant record in master DB; configure B2C policy or federated IdP]

Step 2: Identity Strategy

Option A: Entra ID B2C with Custom Policies

<TechnicalProfile Id="AAD-UserReadUsingObjectId">
  <Metadata>
    <Item Key="UserPrincipalName">user@tenant.onmicrosoft.com</Item>
  </Metadata>
  <OutputClaims>
    <OutputClaim ClaimTypeReferenceId="tenantId" PartnerClaimType="extension_TenantId" />
  </OutputClaims>
</TechnicalProfile>

Option B: Multi-Tenant App Registration

[Configure app to accept users from any organization; resolve tenant from claims]

Step 3: Request Context Middleware

public class TenantMiddleware
{
    private readonly RequestDelegate _next;

    public TenantMiddleware(RequestDelegate next) => _next = next;

    public async Task InvokeAsync(HttpContext context, ITenantResolver resolver)
    {
        var tenantId = await resolver.ResolveTenantAsync(context);
        context.Items["TenantId"] = tenantId;
        await _next(context);
    }
}

Step 4: Data Isolation Patterns

Cosmos DB: Partition by TenantId

var container = cosmosClient.GetContainer("orders", "orders");
var query = new QueryDefinition("SELECT * FROM c WHERE c.tenantId = @tenantId")
    .WithParameter("@tenantId", tenantId);

Azure SQL: Row-Level Security

CREATE FUNCTION dbo.fn_tenantAccessPredicate(@TenantId uniqueidentifier)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN SELECT 1 AS fn_accessResult
WHERE @TenantId = CAST(SESSION_CONTEXT(N'TenantId') AS uniqueidentifier);

CREATE SECURITY POLICY TenantFilter
ADD FILTER PREDICATE dbo.fn_tenantAccessPredicate(TenantId) ON dbo.Orders;

Step 5: Feature Flagging per Tenant

[Use Azure App Configuration with tenant labels; control feature access dynamically]

Step 6: Monitoring & Cost Allocation

[Tag resources with tenant dimension; query Application Insights with tenant filter]

requests
| extend TenantId = tostring(customDimensions["TenantId"])
| summarize RequestCount = count(), AvgDuration = avg(duration) by TenantId
| order by RequestCount desc

Step 7: Throttling & Fair Use

[Implement rate limiting per tenant using API Management policies or Redis sliding window]

Security & Compliance

  • Cross-Tenant Leakage Prevention: Validate tenant context on every data access.
  • Encryption: Transparent Data Encryption (SQL), encryption at rest (Cosmos).
  • Audit Logging: Centralize tenant activity logs in Log Analytics workspace.
  • Data Residency: Use regional deployment stamps for GDPR/compliance requirements.

Scaling Strategies

Challenge Solution
Hot tenant Dedicated shard or reserved capacity
Global reach Azure Front Door + regional stamps
Cost attribution Resource tagging + chargeback reports
Noisy neighbor Request throttling + isolated compute tier

CI/CD Pipeline Considerations

  • Blue-green deployment per stamp
  • Rolling updates with canary tenant validation
  • Database migration coordination across shards

Troubleshooting

Issue: Tenant data visible to another tenant
Solution: Audit query filters; enforce RLS; add integration tests per tenant

Issue: High Azure costs
Solution: Rightsize SKUs; consolidate underutilized tenants into shared pool

Issue: Identity federation breaks
Solution: Test B2C custom policies in sandbox; validate claims mapping

Best Practices

  • Start with pool model; migrate heavy tenants to silo as needed.
  • Abstract tenant resolution early in middleware pipeline.
  • Automate tenant provisioning and configuration.
  • Enforce least privilege across tenant boundaries.

Key Takeaways

  • Pool model maximizes cost efficiency; silo model ensures strict isolation.
  • Identity federation centralizes authentication complexity.
  • Data isolation via partition keys or RLS prevents cross-tenant leakage.
  • Monitoring and cost allocation are critical for SaaS sustainability.

Next Steps

  • Implement tenant lifecycle automation
  • Add self-service tenant portal
  • Explore geo-replication for global tenants

Additional Resources


How will you architect your next SaaS offering?