Back to SDK

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:

  1. Tenant Context Abstractions: Interfaces and value objects for tenant identification
  2. Row-Level Isolation Patterns: Examples and helpers for EF Core query filters
  3. Middleware Integration: ASP.NET Core middleware for tenant context management
  4. Domain Foundation: Well-tested domain layer for tenant operations
  5. Extensibility: Build your tenant resolution and isolation strategy on these foundations
  6. 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, TenantIdentifier for type-safe tenant references
  • Interface Definitions: ITenantEntity, ITenantContext for 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: ITenantResolver for 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

  1. Custom resolvers (highest priority)
  2. HTTP header resolver
  3. JWT claim resolver
  4. Subdomain resolver
  5. Host header resolver
  6. 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

  1. Verify tenant resolution middleware is registered
  2. Check tenant ID is present in header/claim/subdomain
  3. Review resolver priority order
  4. Ensure tenant exists in database
  5. Check tenant status is active

Cross-Tenant Data Access

  1. Verify query filters are applied to all entities
  2. Check raw SQL queries include tenant filter
  3. Review repository implementations
  4. Test with isolation test suite
  5. Enable audit logging to track access

Performance Issues

  1. Ensure tenant ID columns are indexed
  2. Use appropriate isolation strategy
  3. Cache tenant context per request
  4. Review connection string resolution
  5. Monitor database query performance

Migration Guide

Adding Multi-Tenancy to Existing Application

  1. Add Tenant Column:

    ALTER TABLE Orders ADD TenantId varchar(50) NOT NULL DEFAULT 'default';
    CREATE INDEX IX_Orders_TenantId ON Orders(TenantId);
    
  2. Update Entity Models:

    public class Order : ITenantEntity
    {
        public string TenantId { get; set; }
        // ... other properties
    }
    
  3. Configure Tenant Isolation:

    services.AddRiptideTenantIsolation(options => { /* config */ });
    
  4. Apply Query Filters:

    modelBuilder.Entity<Order>()
        .HasQueryFilter(o => o.TenantId == _tenantContext.TenantId);
    
  5. 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


Riptide Tenant Isolation Component - Build secure, compliant-ready multi-tenancy with confidence.