Finbuckle.
MultiTenant has support for data isolation with ASP.NET Core Identity when Entity Framework Core is used as the backing store. It works similarly to Data Isolation with Entity Framework Core except Identity calls into the database instead of your own code.
See the multi-tenant Identity sample project for and example of how to use MultiTenant with ASP.NET Core Identity. These samples illustrate how to isolate tenant Identity data and integrate the Identity UI to work with a route multi-tenant strategy.
Configuring an Identity db context to be multi-tenant is identical to that of a regular db context as described in Data Isolation With Entity Framework Core with a few extra specifics to keep in mind.
The simplest approach is to derive a db context from MultiTenantIdentityDbContext (which itself derives
from IdentityDbContext) and configure Identity to use the derived context.
If for some reason you do not want an Identity entity to be multi-tenant you can override the behavior by
calling the IsNotMultiTenant extension method in OnModelCreating after calling the base class method.
If not deriving from MultiTenantIdentityDbContext make sure to implement IMultiTenantDbContext and call the
appropriate extension methods as described in Data Isolation with Entity Framework Core. In this case it is
required that base class OnModelCreating method is called before any multi-tenant extension methods.
When using a variant of MultiTenantIdentityDbContext any entity designated as multi-tenant will also have the
TenantId property added to its unique index.
Note: Starting in v10, all Identity entity types are configured as multi-tenant by default when you derive from one of the
MultiTenantIdentityDbContextvariants. You generally do not need to add[MultiTenant]or callIsMultiTenantyourself unless you are explicitly overriding behavior on a specific type.
ASP.NET Core Identity introduced passkey support via IdentityUserPasskey<TKey> in Identity schema version 3.
MultiTenant respects this and only configures the passkey entity as multi-tenant when the Identity
schema version is set to 3.
IdentityUserPasskey<TKey> (aka TUserPasskey) is configured as multi-tenant and its unique
indexes include TenantId.TenantId. Depending on your Identity configuration it may not even be part of the model.To enable passkey support with multi-tenant configuration, set the Identity stores schema version to 3:
services.AddOptions();
services.Configure<IdentityOptions>(o =>
{
// Use Identity schema version 3 to enable passkeys
o.Stores.SchemaVersion = new Version(3, 0);
});
No additional configuration is needed in your DbContext; MultiTenantIdentityDbContext will detect the schema version
and configure passkey entities accordingly.
Internally MultiTenant's EFCore functionality relies on a global query filter. Calling the Find method on
an DBSet<T> bypasses this filter thus any place Identity uses this method internally is not filtered by multi-tenant.
Due to this limitation the Identity method UserManager<TUser>.FindByIdAsync will bypass the filter and search across
all tenants in the database. The IdentityUser class uses a GUID for the user id so there is negligible risk of data
spillover, however a different implementation of IdentityUser<TKey> will need to ensure global uniqueness for the user
id.
Identity options can be configured for the IdentityOptions class as described in Per-Tenant Options. Any option that
internally relies on UserManager<TUser>.FindByIdAsync may be problematic as described above. If in doubt check the
Identity source code to be sure.
The Identity option to require a unique email address per user will require email addresses be unique only within the current tenant, i.e. per-tenant options are not required for this.
ASP.NET Core Identity uses cookies for authentication. It uses a slightly different method for configuring cookies, but under the hood standard ASP.NET Core authentication is used.
MultiTenant can isolate Identity authentication per tenant so that user sessions are unique per tenant. See per-tenant authentication for information on how to customize authentication options per tenant.
The ASP.NET Core Identity data model relies on several types which are passed to the database context as generic parameters:
TUserTRoleTKeyTUserClaimTUserTokenTUserLoginTRoleClaimTUserRoleTUserPasskey (Identity schema version 3 only)Default entity types exist such as the IdentityUser, IdentityRole, and IdentityUserClaim, which are commonly used
as the generic parameters. The default for TKey is string. Your app can provide its own entity types for any of these
by using alternative forms of the database context which take varying number of generic type parameters. Simple
use-cases derive from IdentityDbContext types which require only a few generic parameters and plug in the default
entity types for the rest.
In v10, the
MultiTenantIdentityDbContextvariants will configure all provided Identity entity types as multi-tenant by default. The passkey entity (TUserPasskey) is configured only when the Identity schema version is set to 3.
Deriving an Identity database context from MultiTenantIdentityDbContext will use all the default entity types
and string for TKey. All entity types will be configured as multi-tenant.
Deriving from MultiTenantIdentityDbContext<TUser> will use the provided parameter for TUser and the defaults for the
rest. All entity types will be configured as multi-tenant.
Deriving from MultiTenantIdentityDbContext<TUser, TRole, TKey> will use the provided parameters
for <TUser>, TRole, and TKey and the defaults for the rest. All entity types will be configured as multi-tenant.
Deriving from
MultiTenantIdentityDbContext<TUser, TRole, TKey, TUserClaim, TUserRole, TUserLogin, TRoleClaim, TUserToken, TUserPasskey>
will use all provided parameters. All entity types will be configured as multi-tenant, and TUserPasskey is configured
only when the Identity schema version is set to 3.
When providing non-default parameters it is recommended that the provided entity types have the [MultiTenant]
attribute or call the IsMultiTenant builder extension method for each type in OnModelCreating after calling the
base class OnModelCreating.