Production-oriented reference architecture for a secure, multi-tenant SaaS on Azure with hard tenant isolation, Managed Identity-only data access, RBAC, private networking, infrastructure-as-code, and observability baseline.
- Frontend: React + TypeScript + Vite (
apps/web) - Backend: Azure Functions-compatible Node + TypeScript API (
apps/api)- Local dev runtime uses an Express host wrapper (
apps/api/src/local/server.ts)
- Local dev runtime uses an Express host wrapper (
- Database: Azure Cosmos DB SQL API
- IaC: Bicep (
infra/bicep) - Observability: OpenTelemetry + Application Insights
- Local mode: Docker Compose (
docker-compose.yml)
flowchart LR
User[User Browser] -->|JWT| Web[React Web App]
Web -->|Bearer token| Api[Azure Functions API]
Api -->|Managed Identity + RBAC| Cosmos[(Cosmos DB SQL)]
Api -->|Managed Identity + RBAC| Storage[(Storage Account)]
Api -->|Managed Identity + RBAC| KeyVault[(Key Vault)]
Api -->|OTel traces + logs| AppInsights[(Application Insights)]
subgraph Private Network
Api
Cosmos
Storage
KeyVault
end
subgraph Isolation Controls
T1[Tenant claim extraction]
T2[Role guard]
T3[Repository tenant filter]
T4[Cosmos partition key /tenantId]
end
Api --> T1 --> T2 --> T3 --> T4
- Every note includes
tenantId. - Cosmos partition key is
/tenantId. - All reads and writes are tenant-scoped in repository methods.
- JWT tenant claim is required per request.
- Cross-tenant access resolves as not found/denied.
- Unit tests validate cross-tenant rejection.
GET /api/notesPOST /api/notesPUT /api/notes/{id}DELETE /api/notes/{id}GET /api/health
Per request, the API:
- Extracts
userId+tenantId+rolesfrom JWT. - Validates role permissions.
- Enforces tenant scoping in data access.
- Emits structured audit logs with
correlationId.
JWT note:
- The API currently parses JWT payload claims but does not perform JWT signature validation directly.
- Signature validation is expected to be enforced by an upstream identity/auth layer in production.
- Cosmos uses endpoint +
ManagedIdentityCredential(no connection string). - Function storage uses identity-based settings (
AzureWebJobsStorage__*). - Key Vault and Storage access are RBAC-based.
- No application-level Cosmos/Storage/Key Vault connection strings are required.
- Node.js 20+
- npm 10+
- Docker Desktop (for compose mode)
npm install
npm run dev:api
npm run dev:webDirect run defaults:
- API starts on
http://localhost:7071and exposes routes under/api. - Web starts on
http://localhost:5173. - API defaults to
REPOSITORY_MODE=memoryunless you override environment variables.
docker compose up --buildDefault compose mode runs API with in-memory repository.
To start Cosmos emulator service as an optional dependency:
docker compose --profile cosmos up --buildImportant:
- The compose API service is configured with
REPOSITORY_MODE=memoryby default. - Enabling the
cosmosprofile starts the emulator container, but does not automatically switch API repository mode to Cosmos. - To use Cosmos mode locally, provide
REPOSITORY_MODE=cosmosand related Cosmos env vars to the API service.
API environment variables:
REPOSITORY_MODE=memoryorcosmosCOSMOS_ENDPOINT(required whenREPOSITORY_MODE=cosmos)COSMOS_DATABASE_ID(default:TenantNotes)COSMOS_CONTAINER_ID(default:Notes)APPLICATIONINSIGHTS_CONNECTION_STRING(optional for exporter enablement)
./deploy.ps1 -SubscriptionId <subscription-id> -ResourceGroupName <rg-name> -Location eastusOptional parameters:
-ParameterFile(defaultinfra/bicep/parameters.dev.json)
./deploy.ps1 -SubscriptionId <subscription-id> -ResourceGroupName <rg-name> -DryRunIf the resource group does not exist, create it first or run a full deploy once.
infra/bicep/main.bicepinfra/bicep/parameters.dev.json
Provisioned resources include:
- Function App + Premium plan
- Cosmos DB SQL account/db/container (
/tenantIdpartition) - Storage Account (shared key disabled)
- Key Vault (RBAC mode)
- User-assigned Managed Identity
- Role assignments for MI access
- VNet + subnets
- Private endpoints (Cosmos, Storage Blob, Key Vault)
- Private DNS zones and VNet links
- Architecture:
docs/architecture/overview.md - ADR:
docs/architecture/ADR-0001-architecture-overview.md - Cosmos partitioning:
docs/architecture/cosmos-partition-strategy.md - Tenant isolation:
docs/security/tenant-isolation.md - Identity and zero-secrets:
docs/security/identity-zero-secrets.md - Networking:
docs/security/networking-private-endpoints.md - Observability:
docs/operations/observability.md
- JWT signature validation is not implemented in the API process itself; token validation is assumed upstream.
- Test coverage is currently minimal and focused on tenant-boundary/auth helpers in
apps/api/test/tenantBoundary.test.ts. - No automated integration tests currently validate Cosmos DB behavior, Azure RBAC bindings, or Bicep deployments.
- Docker Cosmos emulator profile is optional and not wired by default to API Cosmos mode configuration.