TenantFlow is a production-ready, multi-tenant SaaS backend built with ASP.NET Core, Entity Framework Core, and PostgreSQL, following Clean Architecture principles.
It demonstrates how to correctly implement shared-database multi-tenancy, strict tenant isolation, role-based authorization, audit logging, and integration testing in a real-world SaaS system.
-
Clean Architecture (API, Application, Domain, Infrastructure)
-
Multi-tenancy using shared database + shared schema
-
Tenant resolution via
X-Tenant-IDHTTP header -
Strict data isolation using
TenantId+ EF Core global query filters -
JWT authentication with roles:
SuperAdminTenantAdminUser
-
Tenant lifecycle management (provisioning, deactivation)
-
Audit logging via EF Core SaveChanges interceptor
-
Admin-only audit log querying
-
Background job support with Hangfire
-
PostgreSQL with EF Core migrations
-
Swagger UI secured with JWT + tenant header
-
Dockerized local development
-
Integration tests using Testcontainers + PostgreSQL
TenantFlow follows Clean Architecture, enforcing strict dependency rules:
src/
├── TenantFlow.Api → HTTP layer (controllers, middleware, auth)
├── TenantFlow.Application → Use cases, abstractions, DTOs
├── TenantFlow.Domain → Core business entities and rules
├── TenantFlow.Infrastructure → EF Core, Identity, multi-tenancy, auditing
Api → Application → Domain
Api → Infrastructure → Domain
Application → Domain
The Domain layer has zero dependencies on infrastructure or frameworks.
┌──────────────────────────────┐
│ Client │
│ (Browser / Mobile / API) │
└──────────────┬───────────────┘
│
│ HTTP + JWT + X-Tenant-ID
▼
┌────────────────────────────────────────────────────┐
│ TenantFlow.Api │
│----------------------------------------------------│
│ Controllers │
│ Middleware │
│ • TenantResolutionMiddleware │
│ • Authentication / Authorization │
│----------------------------------------------------│
│ Application Layer │
│----------------------------------------------------│
│ Use Cases / DTOs / Interfaces │
│ • ICurrentUser │
│ • ITenantContext │
│ • IJwtTokenService │
│----------------------------------------------------│
│ Domain Layer │
│----------------------------------------------------│
│ Entities │
│ • Tenant │
│ • TaskItem │
│ • AuditLog │
│ Value Objects & Rules │
│----------------------------------------------------│
│ Infrastructure Layer │
│----------------------------------------------------│
│ EF Core + PostgreSQL │
│ ASP.NET Identity │
│ Audit Interceptors │
│ Hangfire Background Jobs │
└────────────────────────────────────────────────────┘
│
▼
PostgreSQL (Shared DB)
TenantFlow uses a shared database, shared schema approach.
- All tenant-scoped entities include a
TenantId X-Tenant-IDheader is required on tenant-scoped requests- Tenant is resolved per request via middleware
- EF Core global query filters enforce isolation automatically
- No controller or repository manually filters by tenant
This ensures:
- No accidental cross-tenant access
- Centralized enforcement
- Minimal developer error surface
-
JWT Bearer authentication
-
Role-based authorization policies:
PlatformOnly→ SuperAdminTenantAdminOnly→ TenantAdmin or SuperAdmin
-
Tenant membership enforced at request level
-
Deactivated tenants are blocked at middleware level
All changes to tenant-scoped entities are automatically logged using an EF Core interceptor.
Each audit log records:
- Tenant ID
- Entity name and ID
- Operation type (Create, Update, Delete)
- Timestamp
- Acting user
Audit logs are queryable via admin-only API endpoints.
TenantFlow includes integration tests that run against a real PostgreSQL instance using Testcontainers.
Covered scenarios:
- Tenant data isolation
- Role-based authorization
- Tenant deactivation enforcement
- Audit log creation
- Cross-tenant access prevention
This provides high confidence that security and isolation rules work as expected.
- Docker
- Docker Compose
- .NET 8 SDK (for local development)
Create a .env file (an .env.example file is provided):
POSTGRES_DB=tenantflow
POSTGRES_USER=tenantflow
POSTGRES_PASSWORD=tenantflow
CONNECTION_STRING=Host=postgres;Port=5432;Database=tenantflow;Username=tenantflow;Password=tenantflow
JWT_ISSUER=TenantFlow
JWT_AUDIENCE=TenantFlow
JWT_KEY=DEV_ONLY_SUPER_SECRET_KEY_CHANGE_MEdocker compose up --builddotnet ef database update \
--project src/TenantFlow.Infrastructure \
--startup-project src/TenantFlow.Api- API:
http://localhost:8080 - Swagger UI:
http://localhost:8080/swagger
Swagger is configured with:
- JWT Authorization header
X-Tenant-IDheader
Steps:
- Log in via
/api/auth/login - Copy the JWT token
- Click Authorize in Swagger
- Set
X-Tenant-IDfor tenant-scoped endpoints
In Development mode only, the app seeds a platform admin:
Email: admin@tenantflow.dev
Password: Admin123!
Role: SuperAdmin
This allows immediate access to platform endpoints and tenant provisioning.
TenantsAspNetUsers,AspNetRoles(ASP.NET Identity)TaskItemsAuditLogs
All tenant-scoped tables include TenantId.
- Refresh tokens
- Role-based permissions (beyond roles)
- Soft-delete support
- Correlation IDs for tracing
- Rate limiting per tenant
- Background job monitoring per tenant
- OpenTelemetry support
TenantFlow was built to demonstrate:
- Real SaaS backend architecture
- Secure multi-tenant data isolation
- Enterprise-grade authorization
- Testable, maintainable design
- Production-ready patterns in .NET
This project is licensed under the MIT License. You are free to use, modify, and distribute this project with proper attribution.