-
Notifications
You must be signed in to change notification settings - Fork 163
Creating a multi tenant app
This page gives an overview of what you need to do to use AuthP's multi-tenant to build a multi-tenant application.
This is explained in Multi tenant configuration.
You need to create a AuthP Tenant
(see Multi tenant admin service) for each group of data. Each tenant contains a method called GetTenantDataKey
that returns a string used in EF Core's query filter to only show data in the specific AuthP Tenant
.
NOTE: if you are using AuthP's HierarchicalTenant
, then tenants can link to a 'parent' Tenant
- see Multi tenant explained.
When a user logs in AuthP is set up to add a DataKey claim to the logged-in user. This is automatic if you are using the authentication cookie approach, or by AuthP's ITokenBuilder
service that can create the JWT Token for you.
NOTE: an AuthP DataKey starts with dot and then a numeric value, e.g. ".123". Hierarchical tenants may have extra dot+number, e.g. ".123.333.231".
You need to extract the DataKey claim from the ASP.NET Core user. The AuthP library provides a scoped service called GetDataKeyFilterFromUser that capture the ClaimsPrincipal
of the currently logged in user and extract the DataKey claim. AuthP registers the against the IDataKeyFilter
interface.
NOTE: You can create your own service to extract the DataKey if you want, just use a different interface so that it doesn't clash with the IDataKeyFilter
interface service.
You need to provide a second parameter to the constructor in your application DbContext, as shown in the code below.
public class YourDbContext : DbContext, IDataKeyFilter
{
public string DataKey { get; }
public YourDbContext (DbContextOptions<YourDbContext > options, IDataKeyFilter dataKeyFilter)
: base(options)
{
DataKey = dataKeyFilter?.DataKey ?? ".";
}
// rest of code left out.
}
NOTE: You have two options on what to do if the DataKey is null (null means a) no logged in, b) background service call, or c) user hasn't got an assigned tenant):
- Set the DataKey to ".", which will means all the multi-data can be seen (good for admin, but watch out for 'no logged in' user).
- Set the DataKey to a string NOT starting with ".", e.g. "NoAccess". Then no multi-tenant data will be seen by any EF Core query.
You need to configure a global query filter on all the entities that hold tenant's DataKey. You could do this by calling HasQueryFilter
Fluent API method in your configuration, but I recommend you automate this.
- If its a single-level multi-tenant system the user's DataKey much exactly match the DataKey in the multi-tenant entities.
- If its a hierarchical multi-tenant system the DataKey in the multi-tenant entities must startwith the user's DataKey. (Have a look at code inside the
OnModelCreating
method of the RetailDbContext used in Example4's hierarchical multi-tenant system.)
Assuming you are using the automated configuration, each entities that hold tenant data should have an interface to say they have a DayaKey. AuthP provides the IDataKeyFilter
interface, which will work.
You have a few options:
- Your entity constructor uses the
ITenantPartsToExport
interface to add information from the user's Tenant when a new entity if created. See RetailOutlet entity class from the Example4 code as an example. - You override the
SaveChanges
/SaveChangesAsync
methods in your application's DbContext to set a newly created entity's DataKey from the current user's DataKey claim. An example of this approach is shown below.
public class YourDbContext : DbContext, IDataKeyFilter
{
public string DataKey { get; }
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
this.MarkWithDataKeyIfNeeded(DataKey);
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess,
CancellationToken cancellationToken = default(CancellationToken))
{
this.MarkWithDataKeyIfNeeded(DataKey);
return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
/// other code left out
Where the MarkWithDataKeyIfNeeded
extension method will fill in the entity's DataKey
property if
- The entity is being created
- The entity has the
IDataKeyFilter
interface assigned to it - The entity's
DataKey
is null (this test allows a higher level to set a DataKey that is not its own, say to add stock to a shop lower in the hierarchy).
Here is an example the MarkWithDataKeyIfNeeded
extension method
public static void MarkWithDataKeyIfNeeded(this DbContext context, string accessKey)
{
foreach (var entityEntry in context.ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added))
{
if (entityEntry.Entity is IDataKeyFilter {DataKey: null} hasDataKey)
hasDataKey = accessKey;
}
}
- Intro to multi-tenants (ASP.NET video)
- Articles in date order:
- 0. Improved Roles/Permissions
- 1. Setting up the database
- 2. Admin: adding users and tenants
- 3. Versioning your app
- 4. Hierarchical multi-tenant
- 5. Advanced technique with claims
- 6. Sharding multi-tenant setup
- 7. Three ways to add new users
- 8. The design of the sharding data
- 9. Down for maintenance article
- 10: Three ways to refresh claims
- 11. Features of Multilingual service
- 12. Custom databases - Part1
- Videos (old)
- Authentication explained
- Permissions explained
- Roles explained
- AuthUser explained
- Multi tenant explained
- Sharding explained
- How AuthP handles sharding
- How AuthP handles errors
- Languages & cultures explained
- JWT Token refresh explained
- Setup Permissions
- Setup Authentication
- Startup code
- Setup the custom database feature
- JWT Token configuration
- Multi tenant configuration
- Using Permissions
- Using JWT Tokens
- Creating a multi-tenant app
- Supporting multiple languages
- Unit Test your AuthP app