Multi-tenant isolation
EF Core Global Query Filters, per-tenant JWT claims, entity-level RBAC across Admin, Manager and Collaborator. No data crosses a tenant boundary.
EF Core · JWT · RBAC
Read the case study01 / Software engineer
Full Stack .NET Developer
Multi-tenant SaaS architecture
I build the invisible layer of SaaS platforms — the one nobody notices, until it's missing.
02 / Approach
No query ever crosses a tenant boundary.
EF Core Global Query Filters and per-tenant JWT claims, enforced across every module. Isolation is not a check bolted onto each call: it is carried by the data access layer itself.
03 / Expertise
EF Core Global Query Filters, per-tenant JWT claims, entity-level RBAC across Admin, Manager and Collaborator. No data crosses a tenant boundary.
EF Core · JWT · RBAC
Read the case studyAtomic refresh token rotation with reuse detection, session revocation, PostgreSQL optimistic concurrency via xmin with HTTP 409 conflicts, real-time editing presence over SignalR.
JWT · xmin · SignalR
Read the case studyClean Architecture, CQRS, a custom OSS dispatcher replacing 100+ handlers, Application Services, *Request contracts and a refetch-GET pattern.
Clean Architecture · CQRS
Read the case studySerilog with multi-tenant correlation, PostgreSQL integration tests under Testcontainers, GitHub Actions quality gates, Docker deployment on Azure App Service.
Serilog · Testcontainers · Docker
Read the case study04 / Access control
Pick a role and a tenant. The same query returns different rows, and some links become unreachable. This is the access model shipped at TeamXtend, reproduced here on the client.
Navigation
Write — Yes
GET /api/projects
3 rows visible · 2 out of scope
Two distinct mechanisms are at work. The Global Query Filter decides which rows exist for the query at all: a Manager on tenant North cannot read a row from tenant South, even by forging the identifier. Navigation flags decide what is clickable — they improve the experience but protect nothing, because the API re-checks on every call.
05 / Concurrency
Two users edit the same record. Without optimistic concurrency, the second silently overwrites the first and nobody finds out. Try it.
—
db → "Refonte du portail client" · xmin 1042
PostgreSQL exposes a system version counter, xmin, incremented on every write. EF Core compares it before committing: if the value moved, the transaction is rejected rather than overwriting. The presence banner arrives over SignalR and warns before the collision even happens.
06 / Experience
07 / Work
How to guarantee no query can cross a tenant boundary, without adding a filter to every single call.
Multi-tenant · EF Core · JWT
Two users, one record, no silent overwrite. xmin, 409 and real-time presence.
PostgreSQL · SignalR · Testcontainers
A replayed refresh token, a session still valid after a password change. Atomic rotation, reuse detection, cascading revocation.
ASP.NET Core Identity · JWT · Security
MediatR and AutoMapper go dual-license. 100+ handlers to migrate without pausing delivery.
Compliance · CQRS · Mapperly
Designed, built and deployed alone: a website-builder and business-management platform, live for a real client.
Next.js · Payload CMS · Neon
08 / Stack
Core
Architecture
Data
Security and APIs
Observability and testing
Cloud and CI/CD
Working knowledge
Hover for usage context
09 / Notes
Field reports on the AutoCAD .NET API, protecting desktop software, .NET version upgrades and multi-tenant observability.
10 / Questions
By carrying isolation in the data access layer rather than in every query. With EF Core, a Global Query Filter applied to all relevant entities automatically appends the tenant condition to every LINQ query generated. The tenant comes from a validated JWT claim, never from the request body. A developer who forgets a WHERE clause then cannot expose another customer's data.
Related case study — Multi-tenant isolation: isolating without slowing the APIOn PostgreSQL, xmin is preferable in most cases. It is a system column the database already maintains on every write: no column to add, no migration, no risk of forgetting to increment it. An application-level version column keeps its value if you need to stay portable across several database engines.
Related case study — Optimistic concurrency: the conflict you never seeIt depends on actual usage. If you rely on pipelines, notifications and the library's full behaviour, paying for the licence is probably the sensible call. If you only ever call Send, as many teams do, the surface to replace fits in one interface and one dispatch class — a day's work against a recurring cost.
Related case study — Getting off MediatR and AutoMapper, in productionYes, open to opportunities around .NET, multi-tenant SaaS and backend architecture, remote or with relocation. Based in Tunis. Reach out by email at soltanifedi68@gmail.com or via LinkedIn.
11 / Contact
Open to roles and engagements around .NET, multi-tenant SaaS and backend architecture.