The AuthPermissions.AspNetCore library (shortened to AuthP in this article) provides various features that can help you building certain ASP.NET Core applications. The main features are better Roles authorization and back-end code for creating multi-tenant applications (see this series). This article introduces new services in version 3.3.0 of the AuthP library to make it easier for you to add new users and tenants to your application.
This article is part of the series about the AuthP library. The list below provides links to the other articles in this series:
- Finally, a library that improves role authorization in ASP.NET Core
- The database: Using a DataKey to only show data for users in their tenant
- Administration: different ways to add and control tenants and users
- Versioning your app: Creating different versions to maximise your profits
- Hierarchical multi-tenant: Handling tenants that have sub-tenants
- Advanced techniques around ASP.NET Core Users and their claims
- Using sharding to build multi-tenant apps using EF Core and ASP.NET Core
- VIDEO: Introduction to multi-tenant applications (ASP.NET Community standup)
- Three ways to securely add new users to an application using the AuthP library (this article)
- The AuthP library adds extra data (e.g. Roles, Tenants) to an ASP.NET Core user’s claims. To do this we need to link a ASP.NET Core’s user to the AuthP User which contains the extra data.
- This article covers three ways to add a new AuthP user with its extra data to an ASP.NET Core user. The three approaches are:
- Syncing user: Compare all the ASP.NET Core’s users against the AuthP Users and showing the difference to an admin user to decide what to do.
- Invite a user: Sends a Url containing encrypted data holding the user’s email and AuthP data to an invited user. When clicked the new user is added to the application.
- Sign up / versioning: This allows a user to create a new tenant for their company, with the option of selecting which version that fits the company’s needs.
- AuthAdapter: The “Invite a user” and “Sign up / versioning” features need a way to add a new user to the application. These two features rely on a generic AuthAdapter that links the ASP.NET Core user to a new AuthP User. There are two implementations of the AuthAdapter interface to choose one, our create you own versions to the same interface.
From my experience of a building a large multi-tenant for a client I know how much administering of tenants and users there is. As I worked on the AuthP library I found more and more ways to move admin tasks from the application’s support team (referred to as app admin) and out to the users using the application, especially with multi-tenant applications.
One early feature was to allow a user within a tenant (known as a tenant admin user) to manage the other users in the same tenant. This needed careful design to isolate a tenant admin just to their tenant’s users and limit what features they can change. But it has worth the work as tenant users can get a quicker/better response from their tenant admin user and it also reduces the load on the app admin team.
After the basics of the tenant admin user was implemented, I started looking for more self-service features, where the tenant user or tenant admin user could handle admin services on their tenant. In multi-tenant article 2 I showed a way to invite a user to join their tenant, and in multi-tenant article 3 I showed a way to allow a new user to “sign up” to get a new tenant, with the extra feature of having different versions of a tenant (for versions think Visual Studio with its Community, Pro, and Enterprise versions).
The “invite a user” and “sign up / versioning” features significantly reduce the workload on the app admin team by allowing a tenant user to invite new users and automating the setup of a new tenant. But the downside is the original versions for these three features are hand-coded in Example3 in the AuthP repo and would take some work to change it for a different type of multi-tenant or authentication provider. Therefore, the focus of the version 3.3.0 release of the AuthP library is to:
- Provides a generic version of the “invite a user” which works with any type of application
- Provides a generic version of the “sign up / versioning” features which works with any tenant type, i.e. single-level, hierarchical, sharding / not sharding.
With that background the rest of this article will look at how new users can securely add a new user to an application using the AuthP library.
The AuthP library is designed to help applications where users have to log in because they can use certain features. For instance, a multi-tenant application users must log in to be able to access the data in their tenant. And when a new ASP.NET Core user is added we also need to link the ASP.NET Core user to the extra data in the AuthP data.
This means we need ways to handle new ASP.NET Core users being added, and the three main ways are shown the figure below, with a brief list of Pros/Cons.
NOTE: The Green rectangles are ASP.NET Core authentication code, the brown rectangles are AuthP code and the mixed brown / green rectangles represent the AuthAdapter which is a mixture of ASP.NET Core authentication handler and AuthP code.
The following sections describe each approach with their pros and cons. Each section also provides links to the documentation for each of the approaches.
The “Sync users” approach allows an app admin to compare the list of users in the ASP.NET Core authentication provider against the list of AuthP users, and it will show any new, changed or deleted users – see the screenshot below for an example of this would look like.
NOTE: See the documentation on the sync users feature for a fuller explanation on how to set it up and the ways you can add this to your application.
The “Sync users” is the simplest for me to build, so it came out in the first version. Its good when you have a set of users that don’t change much. However, with the new generic “invite a user” and “sign up / versioning” features there are other ways to achieve this.
The biggest con from my perspective is the extra work for the app admin. Not only does the app admin has to add each user they also have to set the AuthP’s Roles / tenant for the user and the sync user process can’t provide that information. In a big multi-tenant application that would be very difficult to manage with this approach.
The other limiting parts of this approach is that it can only work with authentication providers where you can access a list of their valid users. This means this approach won’t work with social-based authentication providers, e.g. Google, Twitter.
The new “invite user” service allows an app admin or a tenant admin to create a secure invite to a user with a given email or username. While creating the invite the admin user can (must) define the AuthP’s Roles and Tenant that the new user should have once they log in. All of this data is encrypted and added to the url which goes to the AcceptInvite page.
The generated url should be sent to the invited user and when they click on url they the user is asked for their email (or username), which is checked against the email in the encrypted data. If everything is OK, then new user is registered as a valid user on the application with the Roles/Tenant setting as found in the encrypted data provided by the invitee.
The “invite a user” can be used to add a new ASP.NET Core user, but if a user with the same email / username is already in your chosen authentication provider, then it will just create the AuthP User linked to the existing ASP.NET Core user.
The screenshot shows the url created by person inviting the user. As you can see the invite is for the user email@example.com which will make that user have access to the “4U Inc.” tenant. You can try this yourself by cloning the AuthP repo and running the Example3 ASP.NET Core project, then logging in as a tenant admin (e.g. admin@4uInc.com) and clicking the “invite user” nav item.
NOTE: The SupportCode -> Invite new user service documentation describes how to setup and use “invite user” service in detail.
This is better than the “sync users” for two reasons. Firstly the ASP.NET Core user data and the AuthP data are set up at the same time – that stops the problem of the “sync users” finding a user but the admin user has to refer to other information to properly set up the AuthP data.
The second, and more powerful reason, is that tenant admin user can safely create an invite for a user to join their tenant. This means you can delegate the adding of a new user to either app admin or tenant admin.
NOTE: The version 3.3.0 “invite a user” service contains lots of checks to make sure that a tenant admin user can only set Roles and a Tenant that the tenant admin user can access.
There aren’t really any downsides of the “invite a user” approach now that there is a generic service is available. The in AuthP version 3.3.0 service works with normal applications and all multi-tenant versions.
This feature consists of two parts:
- The “versioning” part provides a way to create different levels of features (and prices) for the user to choose from. The versioning part is optional, and you can have the same set of features for all tenants it you want.
- The “sign up” part allows a new user to create a new tenant and links the new user to the new tenant created. Once the tenant is created with the user’s chosen version features, then the new user is created as a valid user linked to the new tenant.
Here is a screenshot show the page where the “sign up” user can pick which version that fits your company / organisation. You can try this yourself by cloning the AuthP repo and running the Example3 ASP.NET Core project.
NOTE: The SupportCode -> “sign up / versioning” documentation describes how to setup and use “sign up / versioning” service in detail.
If your application is using AuthP’s Sharding feature, then the “sign on” part need a way to select the correct database and/or database server for the new tenant. There are lots of things to consider when selecting a database (sharding / hybrid, geography located database servers, etc.) so the AuthP library can’t give you a generic service. Instead it provides an interface and you need to write a class to provide this service and register on startup. The docs for the IGetDatabaseForNewTenant explains what you have to do and has a demo service to look at.
The “sign up / versioning” feature contains two parts, both of which are very useful.
The “sign up” part makes signing up for a tenant makes it easy for a company / organization to sign up to the multi-tenant application. This type of self-service provision is used by all the big multi-tenant / SaaS sites because it reduces the barrier to trying out their offerings. It also reduces the load on the app admin team.
The “versioning” part is also used by the all the big multi-tenant / SaaS sites because it increases the number of companies / organizations that sign up. That’s because the potential tenants can sign up the lower cost versions which are more affordable. At the same time people who want the extra features will pay extra, thus increases the profits of the multi-tenant application.
NOTE: You should be aware that AuthP makes it easy to change a tenant’s version because the tenant’s features are mainly controlled by AuthP’s Tenant Roles. The only complex change is to switch the tenant’s data between a shared database and an own database (Sharding). AuthP provides methods that managed database type, but you have to write code to move the tenant data between databases, which is fairly complex.
The only downside is that the “sign up / versioning” is specific to multi-tenant applications.
Like the whole of the AuthP library the “Invite new user” and the “sign up / versioning” features rely on ASP.NET Core’s authentication handlers. Both services add a new user as part of the process. While I could have created the code to use one type of authentication handler that wouldn’t make these services generic.
My solution was to create an interface called
IAddNewUserManager (shown as an AuthAdapter in the earlier diagram) which can provide a common add user service that (potentially) can work with any ASP.NET Core authentication handler. Both the “Invite new user” and the “sign up / versioning” code uses this interface.
In version 3.3.0 of the AuthP library there are only two implementations of the IAddNewUserManager interface that the “Invite new user” or “Sign up / with versioning” features. They are:
- IndividualUserAddUserManager<TIdentity> which works with the individual user accounts authentication handler. You can find an example using this implementation in Example 3 with both Invite new user service and Sign up for a new tenant, with versioning features.
- AzureAdUserManager which works with Azure AD authentication handler. You can find an example using this implementation in Example 5 for the Invite new user service feature. (NOTE: won’t work with Azure AD B2C with social logins).
NOTE: The SupportCode -> Add New User adapter documentation describes in more detail on how this works at providing an adapter of the various ASP.NET Core authentication handlers which provides a common interface to use within applications that use the AuthP library.
The “sync user” approach to linking the ASP.NET Core’s user to the AuthP User is the easiest to build, but it’s not that good in a multi-tenant application as it creates a LOT of work for the app admin team. And it’s the job of the developer to think about things like the (over)load of the app admin team and come up with solutions.
Version 2.0.0 of the AuthP library brought in tenant roles, which allowed tenants to have different features, and in version 2.3.0 examples of the features called “invite a user” and “sign up / versioning” where added to Example 3 application. These features are great help, but the downside of the examples is that they didn’t cover all the possible options.
Version 3.3.0 of the AuthP rewrote the two examples and turned them into generic services which covers all possible situations. It also adds “invite a user” and “sign up / versioning” features to the AuthP library, making it easier for the developer to use them. This meant a few other services has to be created to support these features, with the AuthAdapter being the main one.