Skip to content

Invite new user service

Jon P Smith edited this page Sep 11, 2023 · 9 revisions

Version 3.3.0 of the AuthP library provides a service that allows an existing user (with the right admin Role/Permission) to invite a new user to join the application. This feature, known as "invite a user", provides a quick and secure way to add new users. This feature is especially useful for multi-tenant applications as a tenant admin can invite any colleges join their tenant. It also reduces the load on the app admin users.

NOTE: This section of one of the multi-tenant articles explains the different admin types within a multi-tenant application.

The "invite a user" feature is provided by the IInviteNewUserService and works with any type of application: normal app, or a multi-tenant app of any type, i.e. single-level, hierarchical and / or sharding and via the add New User adapter it can work with different ASP.NET Core authentication handlers.

Example 3 contains the "invite a user" feature which uses individual user accounts authenticate. Example 5 has the "invite a user" feature, but linking to Azure AD for authenticate.

Setup "invite a user" feature

The "invite a user" feature uses two services in the AuthPermissions.SupportCode namespace.

  1. The IInviteNewUserService \ InviteNewUserService, which implements the "invite a user" service.
  2. The IAddNewUserManager service which the InviteNewUserService relies on for adding a new user (see Add New User adapter for more details).

Any code in the AuthPermissions.SupportCode project has to be manually, and the code shown below is taken from Example 3 during the registering of the service to use in your ASP.NET Core application.

//Add the SupportCode services
services.AddTransient<IAddNewUserManager, IndividualUserAddUserManager<IdentityUser>>();
services.AddTransient<ISignInAndCreateTenant, SignInAndCreateTenant>();

Note that the IAddNewUserManager service uses an implementation called IndividualUserAddUserManager<IdentityUser> which works with applications using the individual user accounts authenticate provider. Version 3.3.0 only contains two implementations of the IAddNewUserManager interface. They are:

  • IndividualUserAddUserManager<TIdentity> which works with the individual user accounts authenticate provider.
  • AzureAdUserManager which works with Azure AD authenticate provider (NOTE: won't work with Azure AD B2C with social logins).

More implementations may be added, or you can build your own by implementing the IAddNewUserManager interface.

Creating an invite

Typically some sort of admin user can create an invite - this is controlled by adding a Permission to the Razor Page, Controller actions or Web API that creates the invite. When creating an invite you need three things:

  • Email: This ensures only the person with that email can use the created invite
  • Roles: This allows the admin to define what roles the new user will have.
  • TenantId: If the application is multi-tenant this defines want tenant it will be linked to. What value that the TenantId can be set to depends on what type of admin user is creating the invite
    • If its a single-level multi-tenant and the user is a tenant admin, then you don't have to set the TenantId as the tenant admin's TenantId is used
    • If its a hierarchical multi-tenant and the user is a tenant admin, then you must set the TenantId with a tenant value that is within your group tenant's that you control.
    • If the user is an app admin, then you can:
      • Set it to a Tenant value, which will make the invited user is linked to that tenant
      • Set it to null, which will make the invited user NOT linked to a tenant - useful for adding app admin or customer support users.
  • Expiration: Version 3.4.0 added a expiration time on the invite. The screenshot below shows the default range of expiration times, but you can create your own range.

Here is an example of a single-level multi-tenant application where you define the new user's email and Roles (the TenantId is automatically taken from the tenant admin).

InviteSingleLevel

And here is a code within the Example 3's TenantAdminController

[HasPermission(Example3Permissions.InviteUsers)]
public async Task<ActionResult> InviteUser([FromServices]IAuthTenantAdminService rolesAdmin)
{
    var setupInvite = new InviteUserSetup
    {
        AllRoleNames = await _authUsersAdmin.GetRoleNamesForUsersAsync(User.GetUserIdFromUser())
    }; 

    return View(setupInvite);
}

[HasPermission(Example3Permissions.InviteUsers)]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> InviteUser([FromServices] IInviteNewUserService inviteUserServiceService, 
    InviteUserSetup data)
{
    var addUserData = new AddNewUserDto { Email = data.Email, Roles = data.RoleNames
        TimeInviteExpires = data.InviteExpiration}; 
    var status = await inviteUserServiceService
        .CreateInviteUserToJoinAsync(addUserData, User.GetUserIdFromUser());
    if (status.HasErrors)
        return RedirectToAction(nameof(ErrorDisplay),
            new { errorMessage = status.GetAllErrors() });

    var inviteUrl = AbsoluteAction(Url, nameof(HomeController.AcceptInvite), 
       "Home",  new { verify = status.Result });

    return View("InviteUserUrl", new InviteUserResult( status.Message, inviteUrl));
}

//Thanks to https://stackoverflow.com/questions/30755827/getting-absolute-urls-using-asp-net-core
public string AbsoluteAction(IUrlHelper url,
    string actionName,
    string controllerName,
    object routeValues = null)
{
    string scheme = HttpContext.Request.Scheme;
    return url.Action(actionName, controllerName, routeValues, scheme);
}

On clicking the Create Invite button it will return a URL containing the new user's settings in an encrypted parameter called verify - see this example in the screenshot below. Note that if you set a expiration time, then the message tells you went it will expires.

InviteSingleLevel

This should be sent to the invited user.

How to use the URL to add the new user

The previous step created a URL for the invited user. This should be linked to a action / page which can be accessed by a user who isn't logged in.The invited user then has to provide their email, which is checked against the encrypted settings (this stops a invite by being used by another user). Depending on the type of authentication provider you are using you might need to provide a password (Azure AD version creates a temporary password for the first login, where you are asked to replace with password only known to you). The screenshot below shows the Example 3 version.

InviteSingleLevel

The code from Example 3 to register the new user is shown below

[AllowAnonymous]
public ActionResult AcceptInvite(string verify)
{
    return View(new AcceptInviteDto { Verify = verify });
}

[AllowAnonymous]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> AcceptInvite(
    [FromServices] IInviteNewUserService inviteUserServiceService,
    string verify, string email, string userName, string password)
{
    var status = await inviteUserServiceService.AddUserViaInvite(verify, email, null, password);
    if (status.HasErrors)
        return RedirectToAction(nameof(ErrorDisplay),
            new { errorMessage = status.GetAllErrors() });

    return RedirectToAction(nameof(Index),
        new { message = status.Message });
}

Additional resources

Articles / Videos

Concepts

Setup

Usage

Admin

SupportCode

Clone this wiki locally