developers

User Onboarding Strategies in a B2B SaaS Application

Implement effective B2B SaaS user onboarding strategies with Auth0. Learn about self-service, admin provisioning, and JIT flows, plus a Blazor invitation example.

For developers building B2B SaaS applications, a seamless user onboarding experience is crucial for customer satisfaction and adoption. A clunky or complicated onboarding process can lead to frustration and churn, while a smooth and intuitive one can set the stage for a long and successful customer relationship.

This article explores various user onboarding strategies for B2B SaaS applications, with a focus on the solutions offered by Auth0. We will delve into different onboarding flows and provide a practical guide on how to implement member invitations in a Blazor application.

User Onboarding Strategies

When it comes to onboarding users to your B2B SaaS application, there is no one-size-fits-all solution. The best approach will depend on your specific application, your customers' needs, and their technical capabilities. Auth0's Organizations feature provides the flexibility to support various onboarding models.

Self-Service vs. Admin Provisioning

A fundamental choice in user onboarding is between self-service and administrator-led provisioning.

  • Self-Service: In a self-service model, users can sign up for the application on their own. This is a common approach for applications that want to offer a frictionless trial experience. To maintain control, you can implement an approval workflow where administrators from the customer's organization need to approve new sign-ups.
  • Admin Provisioning: In an admin-provisioning model, an administrator from the customer's organization invites users to the application. This gives the customer more control over who has access to the application and is a common requirement in enterprise settings.

Onboarding Flows with Auth0 Organizations

Auth0 Organizations allows you to model your B2B customers as distinct entities within your Auth0 tenant. This enables you to manage users, connections, and policies on a per-organization basis. Here are two common user onboarding flows you can implement with Auth0 Organizations:

  1. Invitation Flow: An administrator from the customer's organization can invite users to join their Organization within your application. The user receives an email with an invitation link. When they click the link, they are guided through the signup or login process and are automatically added to the correct Organization.
  2. Just-in-Time (JIT) Provisioning: For enterprise customers using their own identity providers (like Azure AD or Okta), JIT provisioning is often the preferred method. With JIT, a user account is automatically created in your application the first time the user logs in through their corporate Single Sign-On (SSO). This eliminates the need for manual user creation and streamlines the onboarding process.

Inviting Users to an Organization

Let's take a closer look at the invitation flow, a common and effective onboarding strategy. With Auth0, you can invite members to an Organization using either the Auth0 dashboard or the Management API.

In the rest of the article, you will learn how to invite a new user to join an Organization programmatically. Basically, the invitation process works as shown in the following diagram:

Diagram illustrating the multi-step user invitation process for B2B SaaS applications using Auth0 Organizations.

Following the numbered lines in the diagram:

  1. An administrator initiates an invitation from your application.
  2. Your application uses the Auth0 Management API to create an organization invitation.
  3. Auth0 sends an email to the user with a unique invitation link.
  4. The user clicks the link, which directs them to your application's login page.
  5. After the user signs up or logs in, they are automatically added to the organization.

To implement this flow, you need to configure an "Application Login URI" in your Auth0 application settings. This is the URL in your application that will handle the invitation acceptance.

For more details, refer to the Auth0 documentation on inviting organization members.

Leverage Auth0's authentication and authorization services in your .NET applications.

DOWNLOAD THE FREE EBOOK.NET Identity with Auth0

Implementing User Invitations

Now, let's see how to add support for inviting organization members to the sample B2B Blazor application, as featured in previous Auth0 blog posts:

The Blazor application allows a new customer to subscribe and create a new Organization, but currently, they can add new users only via the Auth0 dashboard. With the changes we are about to implement, you will enable the Organization administrator to invite users directly from the application.

Prerequisites

If you don’t want to implement the sample application from scratch following the previous articles, you can download it by running the following command:

git clone --branch member-invite --single-branch https://github.com/andychiare/MyBlazorSaaS

Follow the instructions in the attached README file to register and configure the application with Auth0. If you don't have an Auth0 account, you can sign up for free.

To enable the application to send invitations for joining an Organization, you need to add a new permission for your application.

Assuming you have configured your application according to the mentioned README file, navigate to your Auth0 dashboard and locate the ManagementClient application under the Applications > Applications section.

On the application's configuration page, select the APIs tab and expand the area below the Auth0 Management API item. Then, select the create:organization_invitations permission and click the Update button to save your changes.

Implementing the invitation feature

Now we are ready to implement the invitation feature. As the first step, create a new page named Invite.razor under the MyBlazorSaaS/Components/Pages/Dashboard folder and start adding the following code:

@page "/dashboard/users/invite"
@attribute [Authorize(Roles = "admin")]
@rendermode InteractiveServer

@using Auth0.ManagementApi
@using System.ComponentModel.DataAnnotations
@using Auth0.ManagementApi.Models
@using Microsoft.Extensions.Configuration

@inject IAuth0Management auth0Management
@inject IConfiguration Configuration

<h3>Invite a New User</h3>

<div>
 <div class="form-group row my-2">
   <label for="email" class="col-sm-4 col-form-label">EmailAddress</label>
   <div class="col-sm-6">
     <InputText
       type="email"
       @bind-Value="EmailAddress"
       id="email"
       class="form-control"/>
   </div>
 </div>

 <div class="form-group row my-2">
   <label for="role" class="col-sm-4 col-form-label">Role</label>
   <div class="col-sm-6">
     <InputSelect id="role" class="form-select" @bind-Value="RoleId">
           <option value="">Select a role...</option>
           @foreach (var role in availableRoles)
           {
               <option value="@role.Id">@role.Name</option>
           }
     </InputSelect>
   </div>
 </div>

 <button class="btn btn-primary" @onclick="HandleInvite">Send Invite</button>
</div>

@if (!string.IsNullOrWhiteSpace(successMessage)) {
 <div class="alert alert-success" role="alert">
   @successMessage
 </div>
}

@if (!string.IsNullOrWhiteSpace(errorMessage)) {
 <div class="alert alert-danger" role="alert">
   @errorMessage
 </div>
}

This is the UI part of the user invitation page. Its relative path is /dashboard/users/invite, and it is accessible only to administrators. It defines a textbox for the new user’s email address and a select box with the roles that can be assigned to users. The last two if statements control the visibility of the success and error messages.

For the sake of simplicity, no data validation is performed on the data submitted through the form, but this must be done in production code, of course.

The rendering of this page looks like the following picture:

User onboarding invitation form in a B2B SaaS Blazor application with fields for email and role.

Now, let’s implement the behavior behind this UI by adding the following code to the Invite.razor page right after the existing code:

// ...existing code...

@code {
 [CascadingParameter]
 private Task<AuthenticationState>? authenticationState { get; set; }
 private ManagementApiClient? managementClient;
 private string EmailAddress = "";
 private string RoleId = "";
 private string OrganizationId = "";
 private string OrganizationName = "";
 private string successMessage = "";
 private string errorMessage = "";
 private IEnumerable<Role>? availableRoles = [];


 protected override async Task OnInitializedAsync()
 {
   if (authenticationState is not null)
   {
     var state = await authenticationState;

     if (state.User?.Identity?.IsAuthenticated??false)
     {
       OrganizationId = state.User?.FindFirst(c => c.Type == "org_id")?.Value??String.Empty;
       if (!string.IsNullOrEmpty(OrganizationId))
       {
         managementClient = await auth0Management.getClient();
         var organization = await managementClient.Organizations.GetAsync(OrganizationId);
         OrganizationName = organization.DisplayName;

         availableRoles = await managementClient.Roles.GetAllAsync(new GetRolesRequest());
       }
     }
   }
 }

   private async Task HandleInvite()
   {
       try
       {
           var inviterName = $"{OrganizationName} Admin";

           var invitation = new OrganizationCreateInvitationRequest
           {
               Inviter = new OrganizationInvitationInviter { Name = inviterName },
               Invitee = new OrganizationInvitationInvitee { Email = EmailAddress },
               ClientId = Configuration["Auth0:ClientId"],
               Roles = new List<string> { RoleId }
           };

           await managementClient.Organizations.CreateInvitationAsync(OrganizationId, invitation);

           successMessage = $"Invitation sent successfully to {EmailAddress}.";

           EmailAddress = "";
           RoleId = "";
       }
       catch (Exception ex)
       {
           errorMessage = $"Error sending invitation: {ex.Message}";
       }
   }
}

The OnInitializedAsync() method sets the stage for the invitation creation. It retrieves the ID and name of the current Organization, gets an instance of the Auth0 Management client, and loads the user roles to be displayed in the select box.
The HandleInvite() method, triggered by the “Send Invite” button, actually creates and sends the invitation to the user. It prepares an invitation request through an instance of the OrganizationCreateInvitationRequest class and sends it to the user via the CreateInvitationAsync() method.

Handling the invitation

Note that the invitation instance created in the HandleInvite() method needs the value of the application’s client ID among the other parameters. This is necessary to resolve the application’s login endpoint mentioned earlier.

When the user receives the email with the invitation link, they need to navigate to that endpoint to accept the invitation and sign up to the Organization. So, your application must handle the invitation to complete the user onboarding. Fortunately, the Blazor sample app you are using has already implemented it.

For more information on this, please refer to the "Manage User Invitations" section of the Add Auth0 Organizations to Your B2B Blazor Web App blog post.

The final touch

To make the Invite.razor page accessible to the Organization's admin, you need to modify the current simple dashboard page of the Blazor application. You will simply add a button that lets the admin navigate to the Invite.razor page.
Open the Home.razor page in the MyBlazorSaaS/Components/Pages/Dashboard folder and apply the changes highlighted below:

@page "/dashboard"
@attribute [Authorize(Roles = "admin")]
@* 👇 new code *@
@rendermode InteractiveServer

@inject IAuth0Management auth0Management
@* 👇 new code *@
@inject NavigationManager navManager

<PageTitle>Dashboard</PageTitle>

<h1>Admin dashboard</h1>

 <div>
   @* ...existing code... *@
 </div>
@* 👇 new code *@
 <div class="form-group row my-2">
   <div class="col-sm-4"></div>
   <div class="col-sm-6">
     <button class="btn btn-primary" 
             @onclick="@(() => navManager.NavigateTo("/dashboard/users/invite"))">
            Invite a new user
      </button>
   </div>
 </div>
</div>
@* 👆 new code *@

@* ...existing code... *@

This first change concerns the page’s render mode. Since we are going to allow the admin to click a button to navigate to the invitation page, you need to enable interactivity.
Then, you injected the NavigationManager service to enable page navigation.
Finally, you added a section showing the button to navigate to the invitation page.

This will show as follows:

Admin dashboard for B2B SaaS application showing a button to invite new users for simplified user onboarding
Now you have everything ready to test your user onboarding through invitation!

Conclusion

Choosing the right user onboarding strategy is a critical decision for any B2B SaaS application. By leveraging Auth0's powerful and flexible Organizations feature, you can implement a variety of onboarding flows that cater to the diverse needs of your customers. Whether you opt for a manual invitation process, a self-service model, or a fully automated JIT provisioning workflow, Auth0 provides the tools you need to create a seamless and secure onboarding experience for your users.

While this article showed how to implement user onboarding through invitation in .NET, and specifically with the Blazor framework, you are invited to explore other user onboarding approaches and implementations. For example, the SaaStart app includes an implementation of the invitation onboarding in Next.js.

By the way, you can download the final code of the Blazor project created in this article on this GitHub repository.