Tenant Isolation Component
SDK Tenant Isolation Component
Overview
The Riptide SDK Tenant Isolation Component provides foundational abstractions and patterns for building multi-tenant .NET 8.0 applications. It offers domain models, interfaces, and helper utilities to manage tenant context and implement row-level data isolation. This component serves as a building block for SaaS applications requiring tenant-aware functionality.
Purpose
Multi-tenant SaaS applications require consistent tenant context management and data isolation patterns. Traditional approaches often result in error-prone manual tenant checking and inconsistent implementations. Riptide Tenant Isolation provides:
- Tenant Context Abstractions: Interfaces and value objects for tenant identification
- Row-Level Isolation Patterns: Examples and helpers for EF Core query filters
- Middleware Integration: ASP.NET Core middleware for tenant context management
- Domain Foundation: Well-tested domain layer for tenant operations
- Extensibility: Build your tenant resolution and isolation strategy on these foundations
- Clean Architecture: Separation of concerns with domain-driven design
Important Scope Note: This component provides abstractions, domain models, and patterns—not a complete multi-tenancy framework. You'll implement tenant resolution, database isolation, and business rules specific to your architecture.
Key Capabilities
Tenant Context Abstractions
- Domain Value Objects:
TenantId,TenantIdentifierfor type-safe tenant references - Interface Definitions:
ITenantEntity,ITenantContextfor consistent patterns - Attribute Support:
[RequiresTenant]attribute for marking tenant-aware operations - ASP.NET Core Middleware: Base middleware for tenant context management in HTTP pipeline
- Dependency Injection: Register tenant context services with proper scoping
Row-Level Isolation Helpers
- EF Core Patterns: Example implementations of query filters for tenant isolation
- Entity Conventions: Base classes and interfaces for tenant-aware entities
- Repository Examples: Sample tenant-aware repository patterns
- Query Filter Helpers: Utilities to apply tenant filters consistently
Implementation Required: You'll need to implement EF Core global query filters, DbContext configuration, and entity conventions for your specific data model.
Tenant Resolution Patterns
- Resolution Interface:
ITenantResolverfor custom tenant identification logic - Middleware Base: Foundation for building header, JWT, or subdomain resolvers
- Context Storage: Request-scoped storage for tenant context
- Extensibility: Implement resolvers specific to your authentication strategy
Build Your Own: This component provides interfaces and patterns. You'll implement actual resolvers for headers, JWT claims, subdomains, etc., based on your requirements.
Domain Foundation
- Tenant Entity Models: Base domain entities and value objects
- Exception Types: Tenant-specific exception classes
- Constants: Well-defined constants for tenant operations
- Validation Rules: Domain validation for tenant-related operations
Security Considerations
- Isolation Boundaries: Guidelines for maintaining tenant isolation
- Query Filter Patterns: Examples of secure tenant-aware queries
- Context Validation: Patterns for verifying tenant access rights
- Audit Trail Foundation: Interfaces for building audit logging
Your Responsibility: Actual security implementation (authorization, audit logging, rate limiting) depends on your business requirements and architecture.
Integration Points
ASP.NET Core
// Startup configuration
builder.Services.AddRiptideTenantIsolation(options =>
{
// Configure tenant resolution
options.AddHeaderResolver("X-Tenant-Id");
options.AddJwtClaimResolver("tenant_id");
options.AddSubdomainResolver();
// Set isolation strategy
options.IsolationStrategy = TenantIsolationStrategy.RowLevel;
// Enable validation and audit
options.ValidateTenant = true;
options.RequireAuthentication = true;
options.EnableAuditLogging = true;
});
// Middleware
app.UseRiptideTenantResolution();
Entity Framework Core
public class ApplicationDbContext : DbContext
{
private readonly ITenantContext _tenantContext;
public ApplicationDbContext(
DbContextOptions<ApplicationDbContext> options,
ITenantContext tenantContext) : base(options)
{
_tenantContext = tenantContext;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Apply tenant query filter to all entities
modelBuilder.Entity<Order>()
.HasQueryFilter(o => o.TenantId == _tenantContext.TenantId);
modelBuilder.Entity<Customer>()
.HasQueryFilter(c => c.TenantId == _tenantContext.TenantId);
}
public override int SaveChanges()
{
// Automatically set TenantId on new entities
foreach (var entry in ChangeTracker.Entries<ITenantEntity>())
{
if (entry.State == EntityState.Added)
{
entry.Entity.TenantId = _tenantContext.TenantId;
}
}
return base.SaveChanges();
}
}
Service Implementation
public class OrderService
{
private readonly ITenantContext _tenantContext;
private readonly IOrderRepository _repository;
public OrderService(
ITenantContext tenantContext,
IOrderRepository repository)
{
_tenantContext = tenantContext;
_repository = repository;
}
public async Task<Order> GetOrderAsync(int orderId)
{
// Repository automatically filters by current tenant
var order = await _repository.GetByIdAsync(orderId);
// Explicit validation (optional, repositories handle this)
if (order.TenantId != _tenantContext.TenantId)
{
throw new UnauthorizedAccessException("Access denied");
}
return order;
}
}
Common Use Cases
B2B SaaS Applications
Provide complete data isolation for business customers. Each customer (tenant) has strict separation from other customers. Support enterprise requirements for data residency and compliance.
Multi-Brand Platforms
Run multiple brands on same infrastructure with tenant isolation. Each brand appears as separate application with own data, configuration, and customization while sharing underlying codebase.
Regulated Industries
Meet strict compliance requirements (HIPAA, SOC 2, GDPR) with audit trails, data isolation, and access controls. Demonstrate compliance through comprehensive logging and isolation guarantees.
White-Label Solutions
Provide white-label SaaS to partners. Each partner's customers are isolated tenants. Support partner-specific branding, configuration, and integrations while maintaining core platform.
Freemium to Enterprise
Start with row-level isolation for freemium users, upgrade enterprise customers to schema-per-tenant or database-per-tenant for enhanced isolation and performance guarantees.
Technical Specifications
Isolation Strategies
-
Database-per-Tenant: Separate database for each tenant
- Pros: Maximum isolation, easy backup/restore, customization
- Cons: Higher cost, connection management complexity
- Use: Enterprise tenants, regulated industries
-
Schema-per-Tenant: Separate schema in shared database
- Pros: Good isolation, better resource utilization
- Cons: Schema management overhead
- Use: Mid-market tenants, balanced approach
-
Row-Level Security: Shared tables with tenant discriminator
- Pros: Maximum efficiency, simple management
- Cons: Requires careful query filtering
- Use: Freemium, SMB tenants, high tenant count
Tenant Resolution Order
- Custom resolvers (highest priority)
- HTTP header resolver
- JWT claim resolver
- Subdomain resolver
- Host header resolver
- Default tenant (if configured)
Performance
- Context Resolution: Designed to resolve tenants quickly using lightweight resolvers—measure against your authentication strategy
- Query Filtering: Pair the provided patterns with proper indexing to keep row-level isolation efficient
- Context Propagation: Tenant context is passed as ambient state to minimize allocations; validate with your profiling tools
- Caching: Cache tenant context per request when it aligns with your security model
- Scalability: Patterns have been exercised with high tenant counts, but run capacity tests for your workloads
Why Riptide Tenant Isolation Component
Business Value
- Regulatory Support: Isolation primitives and audit hooks make compliance conversations easier
- Customer Trust: Demonstrable data separation increases confidence
- Reduced Risk: Patterns help prevent cross-tenant data leaks
- Faster Sales Cycles: Provide tangible evidence of tenant safeguards
- Lower Support Costs: Consistent isolation reduces tenant-related incidents
Technical Excellence
- Automatic Filtering: Eliminate manual tenant checking
- Type Safe: Compile-time validation of tenant-aware code
- Well Tested: Comprehensive isolation tests
- Clean Architecture: Clear separation of concerns
- Performance Conscious: Minimizes manual plumbing; validate with your telemetry
Enterprise Ready
- Multiple Strategies: Choose isolation level per tenant
- Operational Confidence: Use diagnostics and validation to catch misconfigurations early
- Comprehensive Documentation: Security architecture documented
- Audit Trail Hooks: Forward structured events to your compliance store
- Enterprise Support: Professional support available
Configuration Options
Tenant Resolution
options.AddHeaderResolver("X-Tenant-Id", priority: 1);
options.AddJwtClaimResolver("tenant_id", priority: 2);
options.AddSubdomainResolver(priority: 3);
// Custom resolver
options.AddCustomResolver(async context =>
{
// Custom logic to resolve tenant
var tenantId = await ResolveTenantFromDatabaseAsync(context);
return new TenantIdentifier(tenantId);
}, priority: 0);
// Default tenant (for development)
options.DefaultTenantId = "dev-tenant";
options.AllowDefaultTenant = builder.Environment.IsDevelopment();
Isolation Strategy
// Per-tenant configuration
options.IsolationStrategy = TenantIsolationStrategy.RowLevel;
// Or configure per tenant
options.ConfigureTenantStrategy("enterprise-tenant-1",
TenantIsolationStrategy.DatabasePerTenant);
options.ConfigureTenantStrategy("standard-tenant-*",
TenantIsolationStrategy.SchemaPerTenant);
Security Options
options.ValidateTenant = true;
options.RequireAuthentication = true;
options.ValidateTenantStatus = true;
options.ThrowOnMissingTenant = true;
options.ThrowOnInvalidTenant = true;
// Rate limiting
options.EnableRateLimiting = true;
options.RateLimitPerTenant = 1000; // requests per minute
// Audit logging
options.EnableAuditLogging = true;
options.LogTenantAccess = true;
options.LogDataAccess = true;
Connection String Resolution
// Database-per-tenant
options.ConnectionStringResolver = async (tenantId) =>
{
var tenant = await _tenantRepository.GetByIdAsync(tenantId);
return tenant.ConnectionString;
};
// Schema-per-tenant
options.SchemaNameResolver = (tenantId) =>
{
return $"tenant_{tenantId}";
};
Best Practices
Do's
- ✅ Always use tenant context from DI, never store statically
- ✅ Apply query filters to all tenant-aware entities
- ✅ Test cross-tenant access prevention rigorously
- ✅ Use appropriate isolation strategy for tenant tier
- ✅ Audit and log all tenant data access
Don'ts
- ❌ Don't bypass tenant filters with raw SQL without tenant check
- ❌ Don't store tenant context in static fields
- ❌ Don't allow tenant ID to be modified in requests
- ❌ Don't expose tenant enumeration endpoints publicly
- ❌ Don't forget to set TenantId on new entities
Security Best Practices
Query Filtering
Always verify tenant filters are applied:
// ✅ Good: Filtered by tenant automatically
var orders = await _context.Orders
.Where(o => o.Status == "Pending")
.ToListAsync();
// ❌ Dangerous: Raw SQL without tenant check
var orders = await _context.Orders
.FromSqlRaw("SELECT * FROM Orders WHERE Status = 'Pending'")
.ToListAsync();
// ✅ Good: Raw SQL with explicit tenant filter
var orders = await _context.Orders
.FromSqlRaw(@"
SELECT * FROM Orders
WHERE Status = 'Pending'
AND TenantId = {0}", _tenantContext.TenantId)
.ToListAsync();
Authorization
Always verify user belongs to tenant:
public async Task<IActionResult> GetOrder(int orderId)
{
// Verify user is authenticated
if (!User.Identity.IsAuthenticated)
return Unauthorized();
// Tenant context is resolved from user's token
var order = await _orderService.GetOrderAsync(orderId);
// Repository automatically filtered by tenant
// No additional check needed if repository is properly implemented
return Ok(order);
}
Testing Strategies
Isolation Tests
[Fact]
public async Task GetOrder_ShouldNotReturnOrderFromDifferentTenant()
{
// Arrange
var tenant1 = new TenantIdentifier("tenant1");
var tenant2 = new TenantIdentifier("tenant2");
// Create order for tenant1
using (var scope1 = CreateScopeWithTenant(tenant1))
{
var repository = scope1.ServiceProvider.GetRequiredService<IOrderRepository>();
await repository.CreateAsync(new Order { Id = 1, Name = "Order 1" });
}
// Act: Try to access tenant1's order as tenant2
using (var scope2 = CreateScopeWithTenant(tenant2))
{
var repository = scope2.ServiceProvider.GetRequiredService<IOrderRepository>();
var order = await repository.GetByIdAsync(1);
// Assert: Should not find order
Assert.Null(order);
}
}
Cross-Tenant Leak Tests
Write tests specifically to verify isolation:
- Attempt to query other tenant's data
- Attempt to update other tenant's data
- Attempt to delete other tenant's data
- Verify query filters cannot be bypassed
- Test tenant switching doesn't leak data
Troubleshooting
Tenant Not Resolved
- Verify tenant resolution middleware is registered
- Check tenant ID is present in header/claim/subdomain
- Review resolver priority order
- Ensure tenant exists in database
- Check tenant status is active
Cross-Tenant Data Access
- Verify query filters are applied to all entities
- Check raw SQL queries include tenant filter
- Review repository implementations
- Test with isolation test suite
- Enable audit logging to track access
Performance Issues
- Ensure tenant ID columns are indexed
- Use appropriate isolation strategy
- Cache tenant context per request
- Review connection string resolution
- Monitor database query performance
Migration Guide
Adding Multi-Tenancy to Existing Application
-
Add Tenant Column:
ALTER TABLE Orders ADD TenantId varchar(50) NOT NULL DEFAULT 'default'; CREATE INDEX IX_Orders_TenantId ON Orders(TenantId); -
Update Entity Models:
public class Order : ITenantEntity { public string TenantId { get; set; } // ... other properties } -
Configure Tenant Isolation:
services.AddRiptideTenantIsolation(options => { /* config */ }); -
Apply Query Filters:
modelBuilder.Entity<Order>() .HasQueryFilter(o => o.TenantId == _tenantContext.TenantId); -
Test Isolation: Write and run isolation tests for all entities
Compliance Considerations
- GDPR Support: Tenant isolation primitives, data export samples, and deletion hooks give you a starting point for data subject requests—extend policies and retention logic to meet your obligations.
- HIPAA Alignment: Combine tenant context guards, logging integration points, and encryption extension samples with your organization's security controls to satisfy HIPAA requirements.
- SOC 2 Readiness: Use the provided interfaces for change tracking, access auditing, and availability monitoring as inputs to your SOC 2 control implementations.
- Audit Trail Hooks: Structured logging and middleware events can be forwarded to your chosen audit store for long-term retention.
- Governance Extensions: Build custom validators, policies, and onboarding workflows on top of the provided abstractions to complete your compliance story.
Support & Resources
- API Reference: TenantIsolation API Documentation
- User Guide: Multi-Tenancy Guide
- Sample Application: Basic Web API Sample
- Specifications: TenantIsolation Specification
Riptide Tenant Isolation Component - Build secure, compliant-ready multi-tenancy with confidence.