Implementing User Exists Validation on Send Verification Code in Azure AD B2C Custom Policy

Introduction:

During sign-up in Azure AD B2C, users must provide an email address and verify it before creating an account. A common requirement is to check whether the email address already exists in the directory and show an error message to the user as early as possible, specifically when they click the “Send verification code” button rather than waiting until they fill out the entire form and click “Create”.

In the original implementation, the duplicate email check was placed inside the ValidationTechnicalProfiles of the self-asserted sign-up technical profile. This meant validation ran only when the user submitted the entire form by clicking the “Create” button. As a result, a user could enter an already-registered email, go through the whole email verification process (send code, receive code, enter code, verify), fill out all remaining fields like password, name, and state, and only then receive the error message “There is another user with this email address.” This created a poor user experience since the user wasted time completing a form that could never succeed.

The goal of this implementation is to move the duplicate email check to the “Send verification code” action so users are immediately informed if the email is already taken before any verification code is sent.

Classic Starter Kit VS. Display Control Starter Kit

Azure AD B2C provides two approaches for handling email verification in custom policies. Understanding the difference between them is essential to solving this problem.

Classic Starter Kit:

The classic (legacy) approach uses the built-in email verification mechanism provided by the SelfAssertedAttributeProvider. When you declare an output claim with PartnerClaimType=”Verified.Email”, Azure AD B2C automatically renders the email verification UI (Send verification code button, verification code input, and Verify code button). The entire verification flow is handled internally by the B2C engine. While this is simple to configure, it offers minimal customization. You cannot inject custom validation logic into the “Send Code” or “Verify Code” steps, as these steps are managed internally by B2C. Any ValidationTechnicalProfile you add to the self-asserted technical profile will only execute when the final submit button (Create/Continue) is clicked.

Display Control Starter Kit:

The Display Control approach uses a VerificationControl to manage the email verification flow, rather than relying on the built-in Verified. In the email mechanism, you define a DisplayControl element with explicit Actions for “SendCode” and “VerifyCode”. Each action can have its own ValidationClaimsExchange, allowing you to chain multiple technical profiles that execute when that specific button is clicked. This gives you complete control over what happens at each step of the verification process. You manually configure the OTP generation, email sending, and code verification using dedicated technical profiles (OneTimePasswordProtocolProvider and AzureMfaProtocolProvider).

Why we need the display control approach:

The fundamental limitation of the classic approach is that ValidationTechnicalProfiles on a self-asserted technical profile only run when the user clicks the submit button (Create/Continue). There is no mechanism in the classic approach to execute custom logic when “Send verification code” is clicked because that button is handled entirely by the B2C internal email verification engine.

To run the duplicate email check at the “Send verification code” step, we must use a Display Control. The Display Control’s SendCode action supports ValidationClaimsExchange, which allows us to insert the AAD directory lookup before the OTP is generated and sent. If the lookup finds that the email already exists, the assertion fails, and an error is displayed to the user immediately, and no verification code is sent. This prevents users from wasting time on a verification process for an email that cannot be used.

Code Changes Explanation:

The following changes were made to the DisplayControlTrustFrameworkExtensions.xml file:

1. New Claim Types

Three new claim types were added to support the Display Control OTP flow:

– “otp”: Stores the generated one-time password code internally. The OneTimePasswordProtocolProvider uses this to generate and later verify the code.

– “verificationCode”: This is the text box where the user enters the verification code they received via email. The Display Control renders it as a text input field.

– “strongAuthenticationEmailAddress”: Required by the AzureMfaProtocolProvider to send the verification email. The MFA provider expects the recipient email address in this specific claim.

2. New Claims Transformation – CopyEmailAddress

A new ClaimsTransformation called “CopyEmailAddress” was added. It uses the FormatStringClaim method to copy the value from the “email” claim into the “strongAuthenticationEmailAddress” claim. This is necessary because the AzureMfaProtocolProvider (which sends the verification email) requires the email address in the “strongAuthenticationEmailAddress” claim, whereas the sign-up form collects it in the “email” claim. This transformation bridges that gap.

3. DisplayControls Section – emailVerificationControl

A new DisplayControls section was added under BuildingBlocks with a single DisplayControl named “emailVerificationControl” of type “VerificationControl”. This control defines:

DisplayClaims:

– The “email” claim (the email input field the user types into)

– The “verificationCode” claim with ControlClaimType=”VerificationCode” (the code input field)

OutputClaims:

– The verified “email” claim that gets passed back to the parent technical profile after successful verification

Actions:

SendCode Action – This is the critical part of the implementation. When the user clicks “Send verification code”, the following technical profiles execute in sequence:

  a) AAD-UserReadUsingEmailAddress-RaiseIfExists (with ContinueOnError=”false”): This reads the directory to check if a user with the given email already exists. If the user exists, the objectId will have an actual value (not “NOTFOUND”), and the AssertObjectIdObjectIdNotFoundAreEqual claims transformation will fail, causing an error to be displayed. Because ContinueOnError is set to false, the subsequent steps (OTP generation and email sending) will not execute.

  b) GenerateOtp: If no existing user was found, this technical profile generates a 6-digit OTP code with a 20-minute expiration and up to 5 retry attempts.

  c) SendOtp: Sends the generated OTP to the user’s email address using the Azure MFA email provider.

VerifyCode Action – When the user clicks “Verify code”, the VerifyOtp technical profile is executed to verify that the user-entered code matches the generated code.

4. New Technical Profiles

GenerateOtp:

Uses the OneTimePasswordProtocolProvider with the GenerateCode operation. Configured to produce a 6-digit numeric code that expires after 1200 seconds (20 minutes). The ReuseSameCode setting is authentic, meaning that clicking “Send new code” without verification will resend the same code rather than generate a new one.

VerifyOtp:

Uses the OneTimePasswordProtocolProvider with the VerifyCode operation. Takes the email (as identifier) and the user-entered verification code (as otpToVerify) and validates them against the previously generated code.

SendOtp:

Uses the AzureMfaProtocolProvider with the OneWayEmail operation. It first runs the CopyEmailAddress input claims transformation to populate the strongAuthenticationEmailAddress claim, then sends the OTP email to that address.

5. Modified LocalAccountSignUpWithLogonEmail-CheckEmailAlreadyExists Technical Profile

The self-asserted sign-up technical profile was modified in several ways:

Before (Classic Approach):

– OutputClaims contained: email with PartnerClaimType=”Verified.Email” (triggers built-in email verification)

– ValidationTechnicalProfiles contained: AAD-UserReadUsingEmailAddress-RaiseIfExists (only runs on Create button click)

After (Display Control Approach):

– Added EnforceEmailVerification metadata set to “false” because verification is now handled by the Display Control, not the built-in mechanism

– Added a DisplayClaims section referencing the “emailVerificationControl” Display Control

– OutputClaims now contains just the “email” claim without the “Verified.Email” partner claim type

– Removed the ValidationTechnicalProfiles section since the duplicate email check is now in the Display Control’s SendCode action

6. Existing Technical Profile – AAD-UserReadUsingEmailAddress-RaiseIfExists (Unchanged)

This technical profile was already in place and required no modifications. It reads from Azure AD using the email address as a lookup key. It sets objectId to “NOTFOUND” as a default value and also sets objectIdNotFound to “NOTFOUND” with AlwaysUseDefaultValue=”true”. The output claims transformation AssertObjectIdObjectIdNotFoundAreEqual compares these two values. If the user does not exist, both values are “NOTFOUND,” and the assertion passes. If the user exists, objectId contains the actual object ID from the directory. At the same time, objectIdNotFound remains “NOTFOUND”, causing the assertion to fail and displaying the error message “There is another user with this email address”.

Summary:

The original implementation used the classic email verification approach, where the duplicate email check was placed in the ValidationTechnicalProfiles of the sign-up technical profile. This only triggered on the Create button click, meaning users could complete the entire email verification and form-filling process before realizing the email was already registered.

By switching to the Display Control approach, we moved the duplicate email check into the SendCode action of a VerificationControl display control. Now, when a user enters an email address and clicks “Send verification code”, the system first checks Azure AD for an existing account with that email. If one is found, an error message is displayed immediately, and no verification code is sent. If the email is available, the OTP is generated and sent as usual.

Gowtham K

Gowtham K has been awarded as MVP(Most Valuable Professional) for 9 times by Microsoft for his exceptional contribution in Microsoft technologies under the category “Developer Technologies & Security” . He has more than 12 years of experience on Microsoft technologies such as C#, ASP.NET MVC, ASP.NET WEB API, ASP.NET Core, MS SQL Server, Azure, Microsoft Entra ID, Azure AD B2C and other technologies such as JavaScript, jQuery, HTML and CSS .He is also a blogger and author of articles on various technologies. He is also a speaker and delivered talk on various technologies like ASP.NET MVC, Azure and Azure DevOps in the public events.

Leave a Reply

Your email address will not be published. Required fields are marked *