Runnable GitHub showcase project for a multi-tenant asset management architecture.
This is a small .NET demo that maps to the architecture topics you have been preparing:
- IdentityService / token service
- JWT creation and validation
- SecurityFilter-style middleware
- Human vs machine identity
- Tenant-aware authorization
- Trading order submission
- Reconciliation batch ingestion
- Outbox pattern
- Idempotency key
- AKS/Helm/Argo CD deployment shape
- Angular frontend
- SignalR live order saga updates
- MFA demo flow
- Optimistic concurrency with SQL
rowversionproduction mapping - Dockerfile for container build
- Terraform module for saga orchestrator autoscaling with KEDA or HPA
- Packable NuGet package for reusable SecurityFilter/JWT context helpers
- GitHub Actions templates for CI, NuGet publishing, and container publishing
This project is good to show in interviews because it connects architecture to running code.
You can say:
I built a small asset management platform demo where IdentityService issues JWTs, a SecurityFilter validates claims, trading and reconciliation APIs enforce tenant/permission checks, and the trading API writes an outbox event with idempotency. The same app includes AKS/Helm/Argo deployment notes for blue-green and canary rollout.
$env:DOTNET_CLI_HOME="$PWD\work\.dotnet"
$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE="1"
dotnet run --project .\src\AssetPlatform.Demo\AssetPlatform.Demo.csproj --urls http://localhost:5080Health check:
Invoke-RestMethod http://localhost:5080/health/readyThe frontend is in:
frontend/asset-platform-ui
Install dependencies and run:
cd .\frontend\asset-platform-ui
npm install
npm startOpen:
http://localhost:4200
The Angular UI demonstrates:
- MFA start and verify flow.
- In-memory token handling.
- API calls with bearer token interceptor.
- SignalR connection using
accessTokenFactory. - Live order saga updates.
- Optimistic concurrency conflict demo using
If-Match.
Dockerfile:
Dockerfile
Build:
docker build -t asset-platform-demo:local .Run:
docker run --rm -p 5080:8080 asset-platform-demo:localDocker Compose real-time run:
cd .\deploy\compose
docker compose up --buildThis starts:
Backend API + SignalR -> http://localhost:5080
Angular UI -> http://localhost:4200
NuGet package project:
src/AssetPlatform.Security
Pack locally:
.\scripts\pack-nuget.ps1Publish to NuGet:
.\scripts\publish-nuget.ps1 -ApiKey "<your-nuget-api-key>"Package created by local verification:
artifacts/nuget/AssetPlatform.Security.0.1.0.nupkg
GitHub Actions publishing:
.github/workflows/publish-nuget.yml
Required repository secret:
NUGET_API_KEY
Local script:
.\scripts\publish-container.ps1 `
-Registry "ghcr.io" `
-Repository "your-org/asset-platform-demo" `
-Tag "0.1.0"GitHub Actions publishing:
.github/workflows/publish-container.yml
Default image target:
ghcr.io/<owner>/<repo>/asset-platform-demo:<tag>
Terraform module:
deploy/terraform/saga-orchestrator
Copy variables:
cd .\deploy\terraform\saga-orchestrator
Copy-Item terraform.tfvars.example terraform.tfvarsApply:
terraform init
terraform plan -var-file terraform.tfvars
terraform apply -var-file terraform.tfvarsAutoscaling modes:
autoscaling_mode = "keda" # Service Bus / event-driven saga workers
autoscaling_mode = "hpa" # CPU-based synchronous workload scaling
autoscaling_mode = "none" # fixed replicasImportant:
Do not attach KEDA and a separate HPA to the same deployment by default.
KEDA creates an HPA internally for ScaledObject.
Use KEDA for queue-driven saga workers.
Use HPA for CPU-driven API pods.
$human = Invoke-RestMethod -Method Post http://localhost:5080/identity/token/human `
-ContentType "application/json" `
-Body '{
"tenantId": "tenant-pe-firm-001",
"userId": "user-trader-001",
"email": "trader@examplepe.com",
"roles": ["Trader"],
"permissions": ["Order.Submit", "Order.Read", "Recon.Exception.Read", "Platform.Outbox.Read"],
"portfolioAccess": ["P1001", "P1002"]
}'Invoke-RestMethod http://localhost:5080/whoami `
-Headers @{ Authorization = "Bearer $($human.access_token)" }Invoke-RestMethod -Method Post http://localhost:5080/trading/orders `
-Headers @{
Authorization = "Bearer $($human.access_token)"
"Idempotency-Key" = "demo-order-001"
} `
-ContentType "application/json" `
-Body '{
"portfolioId": "P1001",
"symbol": "MSFT",
"side": "BUY",
"quantity": 100
}'Run the same command again with the same Idempotency-Key. It returns the same order instead of creating a duplicate.
$system = Invoke-RestMethod -Method Post http://localhost:5080/identity/token/system `
-ContentType "application/json" `
-Body '{
"tenantId": "tenant-pe-firm-001",
"clientId": "external-oms-prod",
"sourceSystem": "ExternalOMS",
"roles": ["OMS"],
"permissions": ["Recon.FileIngest", "Platform.Outbox.Read"]
}'Invoke-RestMethod -Method Post http://localhost:5080/recon/batches `
-Headers @{ Authorization = "Bearer $($system.access_token)" } `
-ContentType "application/json" `
-Body '{
"custodianId": "CUST01",
"businessDate": "2026-07-16"
}'Invoke-RestMethod http://localhost:5080/events/outbox `
-Headers @{ Authorization = "Bearer $($human.access_token)" }$mfa = Invoke-RestMethod -Method Post http://localhost:5080/identity/mfa/start `
-ContentType "application/json" `
-Body '{ "email": "trader@examplepe.com" }'
$verified = Invoke-RestMethod -Method Post http://localhost:5080/identity/mfa/verify `
-ContentType "application/json" `
-Body (@{
challengeId = $mfa.challengeId
code = $mfa.demoCode
tenantId = "tenant-pe-firm-001"
userId = "user-trader-001"
email = "trader@examplepe.com"
} | ConvertTo-Json)Every order has a version.
Invoke-RestMethod -Method Patch http://localhost:5080/trading/orders/<order-id>/status `
-Headers @{
Authorization = "Bearer $($human.access_token)"
"If-Match" = "1"
} `
-ContentType "application/json" `
-Body '{ "status": "ManualOverride" }'If the saga already advanced the order to a newer version, the API returns 409 Conflict.
The identity endpoint simulates IdentityService. It creates internal platform JWTs after external authentication. In production, Okta/Entra would authenticate the user, and IdentityService would enrich claims using tenant and Ewealthmanagement entitlements.
The middleware validates the JWT signature, issuer, audience, and expiry, then builds a request security context. APIs do not parse raw tokens directly.
Every order and recon batch is stored with tenantId from the validated token, not from the request body.
Order submission requires Order.Submit permission, portfolio access, and an idempotency key. The outbox event is created with the order so downstream processing can be reliable.
Recon ingestion is machine-friendly and partitioned by tenant, custodian, and business date. This maps well to Service Bus, KEDA, and idempotent workers in AKS.
| Demo Concept | Production Equivalent |
|---|---|
/identity/token/human |
Okta/Entra login plus IdentityService token exchange |
| Demo JWT signer | Enterprise token service / IdentityService signing keys |
| Middleware | SecurityFilter library |
| In-memory store | Azure SQL / Cosmos DB |
| Outbox list | SQL outbox table + Service Bus publisher |
| System token | Service Principal or Managed Identity |
| Tenant claim | Tenant isolation key |
| Portfolio access claim | EWM entitlement service |
| Helm/Argo folder | AKS deployment pipeline |
| Angular frontend | Trading/recon UI |
| SignalR hub | Live order/trade status stream |
| Version field | Azure SQL rowversion / ETag |
| Dockerfile | AKS container image |
| Terraform KEDA mode | Service Bus queue-depth autoscaling for saga workers |
| Terraform HPA mode | CPU autoscaling for synchronous workloads |
AssetPlatform.Security NuGet |
Reusable SecurityFilter/JWT context package |
| GitHub Actions | CI, NuGet publish, container publish |
Use one of these for GitHub:
asset-platform-identity-trading-recon-demo
fintech-aks-rollout-identity-demo
multi-tenant-asset-management-platform-demo
trading-recon-securityfilter-demo