Azure AD B2C OnTicketReceived newUser Claim

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.


Was this article helpful? Share it with your friends:

Share on email
Share on facebook
Share on twitter
Share on reddit

4 thoughts on “Azure AD B2C OnTicketReceived newUser Claim”

  1. Hi, very interesting post, but I have a doubt: what if executing OnTicketReceivedCallback we have a “failed to write oid to database” result?
    I mean, next time the user will log-in the AAD B2C will not send a “newUser = true” state and the database registration will never took place.
    Is there a remediation for this use case?

    Reply
    • bool isNewUser = claims.FirstOrDefault(x => x.Type == "newUser") == null ? false : true;
      string azureOId = claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value;
      int userId = await context.HttpContext.RequestServices.GetService<Services.ApiService>().GetUserIdByOIdAsync(azureOId);
      if (isNewUser || userId == -1)
      {
         ...
      }

      Reply
    • Suppose you’ve fetched the currently signed-in user’s ID from the database using their Azure Object ID (see my comment from March 29). Then, in the B2CExtensions class you set up in this tutorial, you could do something like the following to add a claim with that ID to their claims principal.
      context.Principal.Identities.FirstOrDefault().AddClaim(new Claim("userId", userId.ToString()));

      Reply

Leave a Comment

Looking for more?

You have visited our site before, and we appreciate you!

If you found the tutorials helpful, enter your email address for more free C# tips and tricks.

Can't get enough C#?

Enter your email address for more free C# tutorials and tips.