Building FoxyInvoice — Chapter 4: Multi-tenancy & data security — the vault
This series is written in the open, from a real production system. This chapter is the vault: identity, isolation, authorization, and the tests that prove it holds. [All chapters and diagrams live in the public repo.] Multi-tenant means many unrelated companies share one running system and one database, each seeing only its own data. Get it right and hosting stays cheap forever. Get it wrong and…
Building FoxyInvoice — Chapter 4: Multi-tenancy & data security — the vault
This series is written openly, from a real production system. Chapter 4 covers multi-tenancy, identity, isolation, authorization, and tests that ensure data security. Multi-tenancy means many unrelated companies share one system and database, each seeing only their own data. Proper implementation prevents issues like Business A accessing Business B's invoices. This chapter details FoxyInvoice's isolation methods and how they are tested.
The threat model includes hackers and the more likely scenario of a developer making a mistake, such as forgetting a tenant filter in a query. Security focuses on making wrong code difficult and breaches easy to detect. Identity is established through three methods: email + password (hashed with Argon2id), Google SSO (OAuth), and short-lived JWT access tokens plus rotating refresh tokens. Passwords are never stored, logged, or emailed.
Isolation is achieved through global EF Core query filters, a save interceptor that stamps every insert, and CI tests that prove no data leakage. Global query filters automatically add WHERE TenantId = current to queries on tenant-scoped entities. An interceptor writes the caller's tenant onto every new row during saves, preventing cross-tenant data access. Integration tests verify zero cross-visibility when logging in as two tenants with overlapping data.
Authorization uses RBAC (roles map to permission strings) and three layers: route guards in the SPA, element-level hiding in templates, and HasPermission checks on the API. Scoped access tokens allow showing an invoice to a client without creating an account. An escape hatch exists for background jobs using IgnoreQueryFilters() with explicit tenant handling.
The vault checklist includes an audit trail (interceptor snapshots before/after JSON for changed entities), nightly pg_dump backups (gzip, size-checked, off-site), no card data storage (Stripe holds payment methods), GDPR compliance (export and deletion features), and no secrets in the repo (gitleaks gate). Security controls are non-negotiable, protecting what matters for a billing product. The vault continues to evolve with real usage.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.