Sign Up New Users Using Azure AD B2C

Michael Collins
Neudesic Innovation
13 min readJan 21, 2022

--

In the last post, we took a (very) deep dive into the language of Azure Active Directory B2C’s custom policies and decomposed the starter kit’s local account sign up or sign in relying party policy and user journey. In this post, I will continue where we left off by building a custom sign up relying party policy that can be used by an application to allow new users to create local user accounts in the Azure Active Directory B2C directory.

This is the fifth post in a series on Azure Active Directory B2C and how to use Azure Active Directory B2C to create an identity management system for a Software-as-a-Service application. If you are new to this series, I recommend reading the earlier posts in the series:

  1. Building Application Identity Solutions using Azure AD B2C
  2. Configure Azure AD B2C For Customization
  3. Understanding the B2C Directory
  4. Sign Up or Sign In Users With Azure AD B2C: Part 1

Revisiting the User Story

In the previous post, I presented the user story for sign up:

As a new user, I will create a user account to use Project Center to manage my project work.

I defined the following acceptance criteria for the story:

  • The user will provide a username and password
  • The user will confirm the password by entering it twice
  • both passwords must match
  • an error will be shown if the passwords are not identical

While the previous post covered decomposing a combined sign up and sign in story, I’m going focus on the sign up scenario and break it out into its own relying party policy and user journey. The reason for doing so comes down to single responsibility. Signing up is enough of a feature to warrant its own flow. And when building an application for consumers, I probably want to attract them specifically to a sign up button or link and it could be confusing if they get led to a sign in page instead, even if it has a sign up link on it.

First: How to Debug Custom Policies

Before we dive deep into our first custom policy, I will divert briefly to discuss how we will debug our custom policy. When developing a relying party policy, you can deploy it in production mode or development mode. When deploying in development mode, you can attach your relying party policy with an Application Insights resource where Azure Active Directory B2C will send log data when the relying party policy executes. From there, you can use Azure Monitor to read or query the logs.

A better way to use the Application Insight data than querying the logs is to use the Azure AD B2C Extension for Visual Studio Code. When editing your relying party policy in Visual Studio Code, you can use the extension to view runs of your relying party policy in the Azure Active Directory B2C tenant and access the log outputs for each orchestration step and technical profile that executes along the user journey.

Before you begin creating a custom policy, I recommend creating an Application Insights resource that you will use for debugging your Azure Active Directory B2C relying party policies. To create an Application Insights resource:

  1. Sign into Azure Portal and switch to the directory where your main Azure subscription lives. This will not be your B2C tenant’s directory.
  2. Click the Create a resource button and search for Application Insights.
  3. Click the Create button.
  4. In the form, select a resource group. It’s ok if you want to use the resource group for your Azure Active Directory B2C tenant.
  5. Enter a name and pick a region for your Application Insights service.
  6. For the Resource mode field, choose Workspace-based.
  7. For the Log Analytics Workspace field, choose the default workspace that is shown.
  8. Click the Review + create button.
  9. Click the Create button to create the Application Insights workspace.

After the Application Insights resource is created, open the resource in the Azure Portal. Then follow these steps:

  1. On the Overview tab, make note of the Instrumentation Key field. You will need this value.
  2. In the left navigation bar, scroll down if necessary and click the API Access link.
  3. Make note of the Application ID value. You will need this value later.
  4. Click the Create API Key button.
  5. Enter a description for the API key.
  6. Check the Read telemetry checkbox.
  7. Click the Generate key button.
  8. Make note of the API Key value. You will need this later.

Create a directory that you will be using for development of your Azure Active Directory B2C relying party policies and open the directory in Visual Studio Code. Follow these steps:

  1. With Visual Studio Code, create a file named signup.xml.
  2. With signup.xml opened in the editor, you should see an expandable item labeled AZURE AD B2C TRACE (APP INSIGHTS) in the Explorer tab. Expand it.
  3. Click on the Settings button that looks like a gear.
  4. In the Application Insights Settings view that opens up, enter the Application ID value for your Application Insights resource into the Your Application ID field.
  5. In the Your Application Key (secret) field, enter the API key that you generated earlier.
  6. Click the Save button.

Visual Studio Code is now configured to monitor your Application Insights resource and let you view the trace logs from the relying party execution. We will look at the logs later as we get into developing the sign up relying party policy.

Building the Sign Up Policy

We are now going to turn our attention to building the sign up policy. Our goal is simple: do the least amount of work to create a user in the Azure Active Directory B2C’s directory that we can use for logging in. I’m going to start by creating the outer container for the policy file using XML:

The <TrustFrameworkPolicy> element is the root element for a custom policy XML document. Note the presence of the DeploymentMode="Development" and UserJourneyRecorderEndpoint="urn:journeyrecorder:applicationinsights" attributes. The presence of these attributes enables debugging by logging trace information to your Application Insights resource. You will want to remove these attributes when you are done developing and ready to deploy the policy into production.

We will start by defining the relying party policy using a <RelyingParty> element to the <TrustFrameworkPolicy> element:

⚠️ Note the <UserJourneyBehaviors> child element. <UserJourneyBehaviors> completes the connection between the relying party policy and Application Insights for debugging and tracing. You will want to comment out or remove the <UserJourneyBehaviors> element before pushing to production.

The sign up relying party policy specifies the <DefaultUserJourney> element that references a user flow named SignUp. The relying party policy then defines a technical profile named PolicyProfile. All relying party policies have a single technical profile named PolicyProfile. Remember from the last post that a technical profile is like an executable function, and PolicyProfile is similar to the main function for a program. When the relying party policy is executed by a client application, Azure Active Directory B2C will execute the PolicyProfile technical profile to perform the action for the relying party. PolicyProfile will display the user journey indicated by the <DefaultUserJourney> element and will output a token. The token will include the claims in the <OutputClaims> element. In this case, the token will include the value of the objectId and tenantId claims. The objectId claim will appear with the name sub in the token. The tenantId claim will be hard-coded to the Azure Active Directory B2C’s tenant object identifier, which is obtained using a claim resolver.

The <Protocol> element indicates that the relying party policy implements the Open ID Connect protocol. This means that when Azure Active Directory B2C creates an HTTP endpoint for the relying party policy, that it will use a handler that understands the Open ID Connect HTTP protocol and how to map the HTTP request to the relying party policy that we are creating.

Building the User Journey

The next thing that we need to build is the SignUp user journey. This user journey will do two things:

  1. Display a form for the user to enter their username and password. When the form is submitted, the user account will be created in the directory.
  2. Send the JSON web token with the user’s identity attributes to the client application.

This journey translates into the following XML:

The SignUp user journey has two <OrchestrationStep> elements that match the two steps above. The first <OrchestrationStep> is a ClaimsExchange step. A ClaimsExchange step exchanges claims with another party. In this case, it is going to execute the ShowSignUpForm technical profile to exchange claims and map the output claims to the claim bag. Remember that claims are like variables for other programs. They are used to store data during the execution of the relying party policy. Technical profiles can accept claims like parameters and can output one or more claims as return values.

The second <OrchestrationStep> element is a SendClaims step. A SendClaims step invokes a technical profile that generates the token that is output by the relying party policy and returned to the client application. Remember that returning a token is the ultimate output of a relying party policy. In this case, the step is going to invoke the JWTIssuer technical profile to generate and output a JSON web token that will be returned to the client application.

Displaying the Sign Up Form

Let us go back to the first orchestration step. We need to start by showing the sign up form in a web browser. In the last post, we looked at how the UI was implemented and saw that Azure Active Directory B2C has a technical profile type called a self-asserted technical profile. A self-asserted technical profile prompts the user to provide claims data, typically using a web form, and then processes the claim values that the user provides when the form is submitted.

The <OrchestrationStep> for the form is a ClaimsExchange step, so we need to create the party that the relying party policy will exchange claims with. We’re going to create a claims provider that implements a self-asserted technical profile to prompt the user for their username and password:

The technical profile becomes a self-asserted technical profile because of the <Protocol> element. Azure Active Directory B2C is a .NET application, and the protocol references the Web.TPEngine.Providers.SelfAssertedAttributeProvider class in the Web.TPEngine assembly to execute the technical profile.

The form that is displayed is controlled by the content definition referenced by the ContentDefinitionReferenceId metadata element. For now, we are going to use the built-in UI for Azure Active Directory B2C. We will customize the web UI in a later post. The api.localaccountsignup identifier is a special identifier that is recognized by the self-asserted technical profile type as being for signing up a user for a local user account in the directory. The self-asserted technical profile will use the correct UI elements and implement the logic for password verification when the api.localaccountsignupidentifier is used. The content definition is shown below:

The self-asserted technical profile will use the <OutputClaims> element to determine which claims to prompt the user to provide values for. In this case, we need the user to provide values for the signInName, newPassword, and reenterPassword claims.

⚠️ According to the starter pack, the self-asserted technical profile looks for the newPassword and reenterPassword names and when it sees them, will implement password verification to ensure that the passwords match. The names should not be changed in your custom policies.

The self-asserted technical profile will look to the claim definitions to determine what kind of fields to display for the claims in the web form. The claims are defined as:

The claim definitions instruct the self-asserted technical profile provider to use a text box field to collect the value of the signInName claim and to use password fields to collect the values of the newPassword and reenterPassword claims.

The newPassword and reenterPassword claims also define restrictions that validate the password entered using a regular expression to ensure that the password is strong enough. The regular expression is based on Azure Active Directory password policies and validate that the passwords match one of the following rules:

  1. Lower case, upper case, or digit
  2. Lower case, upper case, or special character (non-alpha or digit)
  3. Lower case, digit, or special character
  4. Upper case, digit, or special character

Further, the regular expression validates that:

  1. The password uses A-Z, a-z, 0–9, or a special character
  2. . can appear as long as it is not followed by @
  3. The password must be between 8 and 16 characters (inclusive)

When we run the technical profile (later), the web form will look like this:

This image shows the basic sign up form. There are three fields. The first field is for the user’s username. The second and third fields are for the user to enter their password and confirm that the password is correct by entering it again.
A basic sign up form

When the form is submitted, the self-asserted technical profile will look at the <ValidationTechnicalProfiles> element and will execute the technical profiles listed there to validate the claim values that the user provided. We will validate the claims by attempting to create the user object in the directory using the CreateLocalUser technical profile.

Creating the User Directory Object

The CreateLocalUser technical profile is defined below:

The CreateLocalUser technical profile is an Azure Active Directory technical profile. The CreateLocalUser technical profile specifies the Operation metadata key and sets its value to Write to indicate that the technical profile will attempt to create an object in the directory. If the object exists, an error will be raised.

The CreateLocalUser technical profile will attempt to use the username in the signInName claim to find an existing user object in the directory. If the user is not found, then the CreateLocalUser technical profile will create a new user object and will write the displayName, newPassword, and signInName claims to the object. The newPassword claim is mapped to the password attribute in the user object. The signInName claim is mapped to the signInNames.userName attribute in the user object. The passwordPolicies claim is written to the directory object and hard-coded to the value DisablePasswordExpiration which disables the password expiration behavior for the user object.

After the user is created, the technical profile will read the objectId and userPrincipalName attributes from the user object and will output their values to the claim bag.

Notice that we are persisting the displayName field, but the user did not enter a display name in the form. The displayName attribute is required for the user object. For now, I am setting the displayName claim to be the same value as the signInName claim that contains the user’s username. To do this, I am using an input claims transformation that will copy the value in the signInName claim to the displayName claim:

The input claims transformation will execute before the CreateLocalUser technical profile executes.

If the user object is successfully created, then the validation technical profile succeeds, the ShowSignUpForm technical profile succeeds, and the user journey advances to the next <OrchestrationStep>.

Generating the JSON Web Token

The second <OrchestrationStep> of the user journey is a SendClaims step. This orchestration step will execute the JWTIssuer technical profile to generate the JSON Web Token that is returned to the client application with the user’s identity information. The JWTIssuer technical profile is defined below:

The JWTIssuer technical profile will include any claims in the relying party policy’s technical profile’s <OutputClaims> collection in the JSON web token that it generates.

The JWTIssuer technical profile will use the B2C_1A_TokenSigningKeyContainer policy key that we generated in a previous post to cryptographically sign the JSON web token so that it can be authenticated. The B2C_1A_TokenEncryptionKeyContainer policy key will be used to encrypt the value of the refresh token (we’ll cover refresh tokens in a later post on the OAuth 2 protocol).

Create a User

With the user flow created, we can now test signing up and creating a user. I can use the Test application that I created in an earlier post when I set up my Azure Active Directory B2C tenant and which will redirect me to https://jwt.ms so that I can see the token that is output by the relying party policy.

The first thing that we need to do is to upload the custom policy:

:warning: If you have been following along, I haven’t given you the full policy. There are some miscellaneous other definitions that you will need. Please refer to the full source code listing at the end of the article before attempting to upload the custom policy.

  1. Open Azure Portal in a web browser and change to the directory for your Azure Active Directory B2C tenant.
  2. Open the control panel for the Azure AD B2C tenant.
  3. In the navigation list on the left, click on Identity Experience Framework.
  4. Click the Upload custom policy button. This will open a new panel on the right side of the screen.
  5. In the Upload custom policy field, click and find the custom policy XML file on your computer.
  6. Click the Upload button to upload your custom policy.

After uploading your policy, you should see the B2C_1A_SignUp policy appear in the list of custom policies. We can now test that policy in the web browser:

  1. Click the B2C_1A_SignUp policy in the policy list. A view will open up on the right side of the screen.
  2. Choose the Test application and set the reply URL to https://jwt.ms.
  3. Click the Run now button.

A new browser tab or window will open with the sign up form. Enter a username and a password and click the Continue button. After the form processes the information that you entered and creates the user object in the directory, you will be redirected to https://jwt.ms with the identity token and you will be able to inspect it:

This image shows the generated JSON web token that is output by the relying party policy and the decoded form of the token.
The decoded JSON web token

Congratulations! We implemented the simplest sign up feature that we could. This is only a starting point. There’s more that we will add onto the sign up feature. But we’ve made our first leap into building a custom identity management solution using Azure Active Directory B2C. In the next post, we will build the simplest log in form to allow us to log into the application using our username and password.

Complete Source Code

The complete source code for the custom policy is below:

Some of the source code was adapted from the Azure Active Directory B2C Starter Pack repository on GitHub.

--

--

Michael Collins
Neudesic Innovation

Senior Director of Application Innovation at Neudesic; Software developer; confused father