I have a requirement to perform verification with both email and phone I used the Local accounts starter pack and I was able to successfully sign up with email verification and then also checked that the sign in is working with email and password To add phone verification I used Display control and called my custom REST APIs to send and verify OTP. Once I verified the OTP successfully, the verification code for email was not displayed Here are the claims
<!-- This claim is for the MFA with mobile number -->
<ClaimType Id="signInNames.phoneNumber">
<DisplayName>Mobile Number signInNamesphoneNumber</DisplayName>
<DataType>string</DataType>
<UserHelpText>Please enter your mobile number.</UserHelpText>
<UserInputType>TextBox</UserInputType>
</ClaimType>
<!-- This claim is used to store the transformed phone number-->
<!-- country code is added to user entered number using string transformation-->
<ClaimType Id="fullPhoneNumber">
<DisplayName>Mobile Number signInNamesphoneNumber</DisplayName>
<DataType>string</DataType>
<UserHelpText>Please enter your mobile number.</UserHelpText>
</ClaimType>
<!-- This is for SMS OTP-->
<ClaimType Id="verificationCode">
<DisplayName>Mobile Number Verification Code</DisplayName>
<DataType>string</DataType>
<UserHelpText>Please enter the verification code.</UserHelpText>
<UserInputType>TextBox</UserInputType>
</ClaimType>
<!-- This is for the OTP Id, this is sent by the custom OTP Provider-->
<ClaimType Id="pinId">
<DisplayName>Mobile Number Verification Code ID</DisplayName>
<DataType>string</DataType>
<UserHelpText>Verification code ID.</UserHelpText>
</ClaimType>
<ClaimType Id="sendOTPRequestBody">
<DisplayName>JSON request body to send OTP SMS</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="verifyOTPRequestBody">
<DisplayName>JSON request body to send OTP SMS</DisplayName>
<DataType>string</DataType>
</ClaimType>
Here are the transformations
<ClaimsTransformation Id="CreatFullPhoneNumber" TransformationMethod="FormatStringClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInNames.phoneNumber" TransformationClaimType="inputClaim" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringFormat" DataType="string" Value="974{0}" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="fullPhoneNumber" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
<ClaimsTransformation Id="GenerateSendOTPRequestBody" TransformationMethod="GenerateJson">
<InputClaims>
<!-- <InputClaim ClaimTypeReferenceId="signInNames.phoneNumber" TransformationClaimType="to" /> -->
<InputClaim ClaimTypeReferenceId="fullPhoneNumber" TransformationClaimType="to" />
</InputClaims>
<InputParameters>
<InputParameter Id="applicationId" DataType="string" Value="xxxxxxx"/>
<InputParameter Id="messageId" DataType="string" Value="xxxxxxx"/>
<InputParameter Id="from" DataType="string" Value="xxxxxxxxx"/>
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="sendOTPRequestBody" TransformationClaimType="outputClaim"/>
</OutputClaims>
</ClaimsTransformation>
<ClaimsTransformation Id="GenerateVerifyOTPRequestBody" TransformationMethod="GenerateJson">
<InputClaims>
<!-- <InputClaim ClaimTypeReferenceId="signInNames.phoneNumber" TransformationClaimType="to" /> -->
<InputClaim ClaimTypeReferenceId="verificationCode" TransformationClaimType="pin" />
<InputClaim ClaimTypeReferenceId="pinId" TransformationClaimType="pinId" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="verifyOTPRequestBody" TransformationClaimType="outputClaim"/>
</OutputClaims>
</ClaimsTransformation>
Here is the Display control definition
<DisplayControls>
<DisplayControl Id="PhoneVerificationControl" UserInterfaceControlType="VerificationControl">
<DisplayClaims>
<DisplayClaim ClaimTypeReferenceId="signInNames.phoneNumber" Required="true" />
<DisplayClaim ClaimTypeReferenceId="verificationCode" ControlClaimType="VerificationCode" Required="true" />
</DisplayClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInNames.phoneNumber" />
<OutputClaim ClaimTypeReferenceId="pinId" />
</OutputClaims>
<Actions>
<Action Id="SendCode">
<ValidationClaimsExchange>
<ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="InfoBipSendOTP" />
</ValidationClaimsExchange>
</Action>
<Action Id="VerifyCode">
<ValidationClaimsExchange>
<ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="RESTSecure-VerifyOTP" />
</ValidationClaimsExchange>
</Action>
</Actions>
</DisplayControl>
</DisplayControls>
Here are the Technical Profiles referenced in the Display control
<TechnicalProfile Id="InfoBipSendOTP">
<DisplayName>Send OTP as SMS</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ServiceUrl">https://xxxxxx.api.infobip.com/2fa/2/pin</Item>
<Item Key="AuthenticationType">ApiKeyHeader</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="ClaimUsedForRequestPayload">sendOTPRequestBody</Item>
<Item Key="DefaultUserMessageIfRequestFailed">Unable to send OTP.</Item>
<Item Key="UserMessageIfCircuitOpen">Unable to send OTP - API Not Reachable.</Item>
<Item Key="UserMessageIfDnsResolutionFailed">Unable to send OTP - DNS Resolution Failed.</Item>
<Item Key="UserMessageIfRequestTimeout">Unable to send OTP. - Request Timeout</Item>
</Metadata>
<CryptographicKeys>
<Key Id="Authorization" StorageReferenceId="B2C_1A_Infobip2FAApiKey" />
</CryptographicKeys>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CreatFullPhoneNumber" />
<InputClaimsTransformation ReferenceId="GenerateSendOTPRequestBody" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="sendOTPRequestBody" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="pinId" PartnerClaimType="pinId" />
<OutputClaim ClaimTypeReferenceId="verificationCode"/>
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
<TechnicalProfile Id="RESTSecure-VerifyOTP">
<DisplayName>Verify the OTP</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ServiceUrl">https://xxxxxxxapimgmtresource.azure-api.net/myTestFnApp/v1/HttpTriggerVerifyOTP</Item>
<Item Key="AuthenticationType">ApiKeyHeader</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="ClaimUsedForRequestPayload">verifyOTPRequestBody</Item>
<Item Key="DefaultUserMessageIfRequestFailed">Unable to verify the OTP.</Item>
<Item Key="UserMessageIfCircuitOpen">Unable to verify the OTP - API Not Reachable.</Item>
<Item Key="UserMessageIfDnsResolutionFailed">Unable to verify the OTP - DNS Resolution Failed.</Item>
<Item Key="UserMessageIfRequestTimeout">Unable to verify the OTP. - Request Timeout</Item>
</Metadata>
<CryptographicKeys>
<Key Id="Ocp-Apim-Subscription-Key" StorageReferenceId="B2C_1A_RestApiEncryptionKey" />
</CryptographicKeys>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="GenerateVerifyOTPRequestBody" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="verifyOTPRequestBody" />
</InputClaims>
<OutputClaims>
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
Here is the actual self-asserted technical profile that presents the UI for the display control , email and other fields. Kindly ignore the mobile and userName claims below , they are used in custom REST API to check if userName is eligible to login and if the userName and mobile are linked in our custom user database
<TechnicalProfile Id="LocalAccountSignUpWithLogonEmail">
<DisplayName>Email signup</DisplayName>
<Protocol Name="Proprietary"
Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="IpAddressClaimReferenceId">IpAddress</Item>
<Item Key="ContentDefinitionReferenceId">api.localaccountsignup</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaims>
<!-- <InputClaim ClaimTypeReferenceId="email" /> -->
</InputClaims>
<DisplayClaims>
<DisplayClaim DisplayControlReferenceId="PhoneVerificationControl" />
<DisplayClaim ClaimTypeReferenceId="email" Required="true" />
<DisplayClaim ClaimTypeReferenceId="signInNames.userName" Required="true"/>
<DisplayClaim ClaimTypeReferenceId="mobile" Required="true"/>
</DisplayClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="signInNames.userName" Required="true"/>
<OutputClaim ClaimTypeReferenceId="mobile" Required="true"/>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
<OutputClaim ClaimTypeReferenceId="newPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="executed-SelfAsserted-Input" DefaultValue="true" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
<OutputClaim ClaimTypeReferenceId="newUser" />
<!-- Optional claims, to be collected from the user -->
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surName" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="RESTSecure-CheckUserEligibility" />
<ValidationTechnicalProfile ReferenceId="REST-CheckUserMobileLink" />
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonEmail" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>