Skip to main content

What multi-tenancy means in AspFox

One deployed instance of AspFox serves multiple isolated workspaces — called tenants. A user can belong to multiple tenants and switch between them without re-authenticating. The currently active tenant is stored as a first-class JWT claim (tenant_id). Every database query automatically filters to that tenant’s data. Users are global (they have one account), but their membership, role, and permissions are per-tenant. A user can be an Owner in one workspace and a Member in another.

How tenant isolation is enforced

This is the most important thing to understand about the data model. Isolation is not implemented as an API-layer check (“does this user have access to this record?”). It is enforced by EF Core global query filters that automatically append WHERE tenant_id = @currentTenantId to every query against tenant-scoped tables.
TenantContext is a scoped service set once per request by TenantResolutionMiddleware:
The full request flow:

What tenant isolation means architecturally

A user from Tenant A cannot access Tenant B’s data by any means short of modifying the source code. This is not a permission check that can be bypassed by a crafted request. It is an ORM filter that runs on every query. The only way to query across tenants is to call IgnoreQueryFilters() explicitly:
IgnoreQueryFilters() appears in three places in the codebase: admin user management queries, admin tenant management queries, and background jobs (which process all tenants). Nowhere in the normal request path is it called.

The invitation flow

Inviting a user to a workspace:
The REGISTRATION_REQUIRED case is handled on the frontend. When AcceptInvitationCommandHandler returns REGISTRATION_REQUIRED, the frontend stores the invitation token in sessionStorage and redirects to the registration page. After the user registers and verifies their email, the Accept Invitation page reads the stored token and retries the accept call automatically.

How to add a tenant-scoped entity

Every entity that belongs to a single tenant must implement ITenantScopedEntity and have the global query filter applied.
See the Adding a Feature guide for the complete walkthrough including migration, commands, and frontend.