Azure AD B2C OnTicketReceived newUser Claim

AZURE B2C SIGNUP EVENT: AUTOMATICALLY ADD USER TO LOCAL DATABASE

by

This tutorial will teach you how to automatically add an Azure AD B2C user to a local database table when the user completes the signup user flow. In particular, you will learn how to use the OnTicketReceived OpenIdConnectEvents event along with Azure's boolean newUser claim to determine that a user has just registered on your ASP.NET (Blazor, MVC, or Razor Pages) site, and then you will take some action, such as making an API call, based on that signup event.

Configure Azure

It is fairly common practice to track user information in a local database, even when working with an external identity provider, such as Azure AD B2C.

The first thing you must do to catch the signup event is configure Azure to return the "User is new" claim to your application in the signup user flow. To do this, open the Azure Portal to the appropriate directory, navigate to your AD B2C tenant, and locate the signup user flow. From there, load the user flow's application claims, and be sure "User is new" is checked.

In this tutorial, you will be storing the user's Object ID in your local user table, so be sure that claim is enabled as well.

Create User Model

For this tutorial, you will use an entity framework core model to store your user information. The simplest form of this model will hold the ObjectId of the user, as found in Azure AD B2C. That model would look like the following.

public class User
{
	[Key]
	public int Id { get; set; }
	public string UserOId { get; set; }
}

It is outside the scope of this tutorial to demonstrate how to create the API controller and endpoints for working with the User entity. Nor does this tutorial seek to show how to add the entity to your database context. It is sufficient to say that both things must be done.

Create OnTicketReceived Callback

When writing this tutorial, I decided to keep my OnTicketReceived event handler in the same class as my other Azure AD B2C customizations. Feel free to put yours wherever you want, but I will add mine to a static B2CExtensions class.

The OnTicketReceived event is raised any time a user logs in to or registers for an account on your web app. Since you are only interested in performing an action when a user registers, not when they log in, you will first need to check if the user is new. To do this, you must check the claims sent back to your application by Azure.

If a user is new, that is to say they have just successfully completed the signup user flow, a bool newUser = true will be included in the claims list. If the user is not new, this claim will not be present, and you will not need to take any action. Name your event handler OnTicketReceivedCallback. The handler will take the following form.

public async static Task<Task> OnTicketReceivedCallback(TicketReceivedContext context)
{
   //Check if user just completed signup flow
   List<Claim> claims = context.Principal.Claims.ToList();
   bool isNewUser = claims.FirstOrDefault(x => x.Type == "newUser") == null ? false : true;

   //If so, do what needs to be done
   if (isNewUser)
   {
	   //Write objectid to local user table
   }

   return Task.CompletedTask;
}

Write User to DB

In a previous tutorial, you learned about using the HttpClientFactory to register a typed HttpClient that you can use throughout your Blazor web application to make API calls. This tutorial will use that same approach by referencing a CreateUserAsync method in a registered class called ApiService. For a reminder about how configure this setup, please see the HttpClientFactory and Web API tutorial.

First, get the user's object ID from Azure by fetching the value of the NameIdentifier claim type. In my solution, I write this value (and any other values from the User model I wish to be included in the local table) to a new instance of the User object and I pass that object to the ApiService class which will attempt to POST to the API. Your solution may look different.

public async static Task<Task> OnTicketReceivedCallback(TicketReceivedContext context)
{
   //Check if user just completed signup flow
   List<Claim> claims = context.Principal.Claims.ToList();
   bool isNewUser = claims.FirstOrDefault(x => x.Type == "newUser") == null ? false : true;

   //If so, do what needs to be done
   if (isNewUser)
   {
		string userOId = claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value;
		//Write objectid to local user table
		User user = new User() { UserOId = userOId };
		var response = await context.HttpContext.RequestServices.GetService<Services.ApiService>().CreateUserAsync(user);
		if (!response.IsSuccessStatusCode)
		{
			//failed to write oid to database
		}
   }

   return Task.CompletedTask;
}

Register Event Handler

To register the event handler you just created, OnTicketReceivedCallback, as a handler for the OnTicketReceived event, open your Blazor project's Startup.cs file and locate the ConfigureServices() method.

If you enabled Azure authentication when you created the project in Visual Studio, it was likely scaffolded using the AzureADB2C.UI package. To register a handler for the OnTicketReceived event, add the following lines.

services.Configure<OpenIdConnectOptions>(AzureADB2CDefaults.OpenIdScheme, options =>
    options.Events.OnTicketReceived = B2CExtensions.OnTicketReceivedCallback
);

If your handler is located in a different place, be sure to make the appropriate adjustments.

Troubleshooting and Bottom Line

If you run into problems, be sure the event handler assignment is registered after any of its dependencies. For example, it should appear after you have added and configured AzureADB2C authentication. In our example, OnTicketReceivedCallback also references the ApiService HttpClient, so it should also appear after that service is added. Consider placing the event handler assignment at the bottom of ConfigureServices.

Now that you have successfully intercepted the Signup event for your web application, the possibilities are endless.


Don't stop learning!

There is so much to discover about C#. That's why I am making my favorite tips and tricks available for free. Enter your email address below to become a better .NET developer.


Did you know?

Our beautiful, multi-column C# reference guides contain more than 150 tips and examples to make it even easier to write better code.

Get your cheat sheets