Why doesn't FormatLocalizedString work in Azure AD B2C custom policies? - azure-ad-b2c

I need to display a custom message after the code is sent in the password reset flow, I have tried following this https://learn.microsoft.com/en-us/azure/active-directory-b2c/string-transformations#formatlocalizedstring but it does not work. I am doing the following, very similar to the reference.
I add the same claimTransformation
<ClaimsTransformation Id="SetResponseMessageForOTPSent" TransformationMethod="FormatLocalizedString">
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringFormatId" DataType="string" Value="ResponseMessage_EmailExists" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="responseMsg" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
And the localized string
<LocalizedString ElementType="FormatLocalizedStringTransformationClaimType" StringId="ResponseMessage_EmailExists">Code sent to "{0}"</LocalizedString>
Finally I refer to the claimTransformation from a technicalProfile
<TechnicalProfile Id="SendOtp">
<DisplayName>Send 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">Url</Item>
<Item Key="AuthenticationType">ApiKeyHeader</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="ContentDefinitionReferenceId">api.localaccountpasswordreset</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
</InputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="SetResponseMessageForOTPSent" />
</OutputClaimsTransformations>
</TechnicalProfile>
But it doesn't work, it always shows the message "Unable to validate the information provided". What am I doing wrong?

Related

Get singin email from Azure AD in a B2C custom policy

In AAD B2C I have users created with 2 identities. I used graph to create them with this body :
{
"displayName": "John Doe",
"mail":"johndoe19287456#gmail.com",
"identities": [
{
"signInType": "userName",
"issuer": "mytenant.onmicrosoft.com",
"issuerAssignedId": "606198"
},
{
"signInType": "emailAddress",
"issuer": "mytenant.onmicrosoft.com",
"issuerAssignedId": "johndoe19287456#gmail.com"
}
],
"passwordProfile" : {
"password": "Soleil!23",
"forceChangePasswordNextSignIn": false
},
"passwordPolicies": "DisablePasswordExpiration"
}
This allow the user to connect either with an email (johndoe19287456#gmail.com) or an ID (606198).
When a user input his ID and then click on the "Forgot password?" link, I'd like to get the email value from AAD so the user cannot input whatever he wants. But I'd still like it to be "verified" by sending a code to that email address. I have 2 problems :
I can't fnd a way to get the email value from AzureActiveDirectoryProvider
I can't find a way to populate the Verified.Email field (and make it readonly).
Here's a sample of one of the many things I've tried yet.
Building blocks custom claims :
<ClaimType Id="ReadOnlyEmail">
<DisplayName>Verified Email Address</DisplayName>
<DataType>string</DataType>
<UserInputType>Readonly</UserInputType>
</ClaimType>
<ClaimType Id="emailFromAAD">
<DisplayName>Email from AAD</DisplayName>
<DataType>string</DataType>
<UserHelpText />
<UserInputType>Readonly</UserInputType>
</ClaimType>
<ClaimType Id="readOnlySignInName">
<DisplayName>Sign in name</DisplayName>
<DataType>string</DataType>
<UserHelpText />
<UserInputType>Readonly</UserInputType>
</ClaimType>
<ClaimType Id="emailValue">
<DisplayName>Matched mail</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="isEmailBoolean">
<DisplayName>is Email</DisplayName>
<DataType>boolean</DataType>
</ClaimType>
<ClaimType Id="strongAuthenticationEmailAddress">
<DisplayName>string</DisplayName>
<DataType>string</DataType>
<AdminHelpText>Email address that the user can use for strong authentication.</AdminHelpText>
<UserHelpText>Email address to use for strong authentication.</UserHelpText>
<UserInputType>TextBox</UserInputType>
</ClaimType>
Claims transformation :
<ClaimsTransformation Id="CopySignInNameFromReadOnly" TransformationMethod="FormatStringClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="readOnlySignInName" TransformationClaimType="inputClaim" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringFormat" DataType="string" Value="{0}" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInName" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
<!-- If signin name match the regex, it is an email identifier. Oterwise, it'll be considered as username -->
<ClaimsTransformation Id="isEmail" TransformationMethod="setClaimsIfRegexMatch">
<InputClaims>
<InputClaim ClaimTypeReferenceId="readOnlySignInName" TransformationClaimType="claimToMatch" />
</InputClaims>
<InputParameters>
<InputParameter Id="matchTo" DataType="string" Value="[^#]+#[^\.]+\..+" />
<InputParameter Id="outputClaimIfMatched" DataType="string" Value="isEmail" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="emailValue" TransformationClaimType="outputClaim" />
<OutputClaim ClaimTypeReferenceId="isEmailBoolean" TransformationClaimType="regexCompareResultClaim" />
</OutputClaims>
</ClaimsTransformation>
Technical profiles:
<!-- Password reset step 1b - Included in step SelfAsserted-LocalAccountLookup-Combined-PwdReset
That's where the input claim is define. signInName = the Username field on the screen -->
<TechnicalProfile Id="SelfAsserted-LocalAccountLookup-Combined-SignUp">
<DisplayName>Local Account Sign Up</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ContentDefinitionReferenceId">api.selfasserted</Item>
<Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="readOnlySignInName" DefaultValue="{OIDC:LoginHint}" AlwaysUseDefaultValue="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="readOnlySignInName" Required="true" />
<OutputClaim ClaimTypeReferenceId="isEmailBoolean" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CopySignInNameFromReadOnly" />
</OutputClaimsTransformations>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="regexAnalysisUsername" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
<!-- Password reset step 1a. Includes SelfAsserted-LocalAccountLookup-Combined-SignUp
Input claim is defined in this. Here, we define output claims -->
<TechnicalProfile Id="SelfAsserted-LocalAccountLookup-Combined-PwdReset">
<DisplayName>Reset password</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="setting.showCancelButton">false</Item>
<Item Key="IpAddressClaimReferenceId">IpAddress</Item>
<Item Key="ContentDefinitionReferenceId">api.localaccountpasswordreset</Item>
<Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Your account has been locked. Contact your support person to unlock it, then try again.</Item>
</Metadata>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="readOnlySignInName" Required="true" />
<OutputClaim ClaimTypeReferenceId="isEmailBoolean" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="emailFromAAD" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingIdentifier" />
<ValidationTechnicalProfile ReferenceId="regexAnalysisUsername" />
</ValidationTechnicalProfiles>
<IncludeTechnicalProfile ReferenceId="SelfAsserted-LocalAccountLookup-Combined-SignUp" />
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
<!-- Password reset step 1c. Verify signin name on the "Continue" button clicked in the first screen -->
<TechnicalProfile Id="AAD-UserReadUsingIdentifier">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="readOnlySignInName" PartnerClaimType="signInNames" Required="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="emailFromAAD" PartnerClaimType="signInNames.emailAddress" />
<OutputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
<!-- Passwod reset step 2. Only if sign in data was a username -->
<TechnicalProfile Id="LocalAccountDiscoveryUsingUserNameAndValidateStrongAuthenticationEmailAddress">
<DisplayName>Reset password using username</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<!-- Other values maybe defined in localized api.selfasserted.fr and api.selfasserted.en -->
<Item Key="IpAddressClaimReferenceId">IpAddress</Item>
<Item Key="ContentDefinitionReferenceId">api.localaccountpasswordreset</Item>
<Item Key="AllowGenerationOfClaimsWithNullValues">true</Item>
<Item Key="UserMessageIfClaimsTransformationStringsAreNotEqual">An account could not be found for the provided User ID and email combination.</Item>
<Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Your account has been locked. Contact your support person to unlock it, then try again.</Item>
<Item Key="LocalAccountType">Username</Item>
<Item Key="LocalAccountProfile">true</Item>
<!-- Reduce the default self-asserted retry limit of 7 for the reset journey -->
<Item Key="setting.retryLimit">5</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="readOnlySignInName" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="readOnlySignInName" Required="true" />
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" DefaultValue="strongAuthenticationEmailAddress" Required="true" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
<OutputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" />
<OutputClaim ClaimTypeReferenceId="emailFromAAD" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingUserNameAndValidateStrongAuthenticationEmailAddress" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
For now, I get the first screen with the readonly ID working.
The next screen presents the textboxes for email. The Email from AAD shouldn't visible.
But anyway it is empty, showing that it didn't get anything from AAD or I failed to properly store it in the claim bag or pass it down. Note that I have tried getting the value both from "strongAuthenticationEmailAddress" and "signInNames.emailAddress" based on Microsoft documentation but none of it works. Maybe it's in the way I define my output claim with PartnerClaimType in the AAD-UserReadUsingIdentifier profile?
To make it clear for everyone, here's what I'd like to have. A simple page with 2 reaonly fields with a button to send the code and then another one to continue after the code has been verified.
Can anyone help me with this one?
I started from the B2C custom policies starter pack and added customization from this community repo.
UPDATE
Here is what I get from MS Graph when querying for my user :
{
"#odata.context": "https://graph.microsoft.com/v1.0/$metadata#users(identities,id,displayName,mail,surname,userPrincipalName,extension_06a19ccd80a0430c9730c62e4d96c895_ClientID,extension_06a19ccd80a0430c9730c62e4d96c895_requiresMigration)/$entity",
"id": "051e***************",
"displayName": "test - Tests2",
"mail": "johndoe19287456#gmail.com",
"surname": null,
"userPrincipalName": "051ea****************#mytenant.onmicrosoft.com",
"identities": [
{
"signInType": "emailAddress",
"issuer": "mytenant.onmicrosoft.com",
"issuerAssignedId": "johndoe19287456#gmail.com"
},
{
"signInType": "userName",
"issuer": "mytenant.onmicrosoft.com",
"issuerAssignedId": "606198"
},
{
"signInType": "userPrincipalName",
"issuer": "mytenant.onmicrosoft.com",
"issuerAssignedId": "051ea****************#mytenant.onmicrosoft.com"
}
]
}
You must provide the emailFromAAD as an input claim in LocalAccountDiscoveryUsingUserNameAndValidateStrongAuthenticationEmailAddress for it to be pre populated here.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/self-asserted-technical-profile#input-claims
<InputClaims>
<InputClaim ClaimTypeReferenceId="readOnlySignInName" PartnerClaimType="signInNames" Required="true" />
<InputClaim ClaimTypeReferenceId="emailFromAAD" />
</InputClaims>
Now it will be prepopulated on this page.
If you want to force verification of this email, copy it into a read only claim using a claim transform, then do:
<OutputClaim ClaimTypeReferenceId="readOnlyEmailFromAAD" PartnerClaimType="Verified.Email" Required="true" />
I managed to get email address from AAD by following Jas Suri - MSFT advice with a few other things. So just to sum things up here's what I did. I modified the Validation TP AAD-UserReadUsingIdentifier by replacing the output claim emailFromAAD simply by signInNames.emailAddress (no more PartnerClaimType). In the LocalAccountDiscoveryUsingUserNameAndValidateStrongAuthenticationEmailAddress TP, I added the InputClaim emailFromAAD that I populate with a CopyClaim input claims transformation. I also cleaned up the first TP of the journey SelfAsserted-LocalAccountLookup-Combined-PwdReset :
<ClaimsTransformation Id="CopySignInEmailAddressToEmail" TransformationMethod="CopyClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInNames.emailAddress" TransformationClaimType="inputClaim" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="emailFromAAD" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
<TechnicalProfile Id="SelfAsserted-LocalAccountLookup-Combined-PwdReset">
<DisplayName>Reset password</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="setting.showCancelButton">false</Item>
<Item Key="IpAddressClaimReferenceId">IpAddress</Item>
<Item Key="ContentDefinitionReferenceId">api.localaccountpasswordreset</Item>
<Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Your account has been locked. Contact your support person to unlock it, then try again.</Item>
</Metadata>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="readOnlySignInName" Required="true" />
<OutputClaim ClaimTypeReferenceId="isEmailBoolean" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
<OutputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingIdentifier" />
<ValidationTechnicalProfile ReferenceId="regexAnalysisReadOnlyUsername" />
</ValidationTechnicalProfiles>
<IncludeTechnicalProfile ReferenceId="SelfAsserted-LocalAccountLookup-Combined-SignUp" />
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
<TechnicalProfile Id="AAD-UserReadUsingIdentifier">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="readOnlySignInName" PartnerClaimType="signInNames" Required="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
<OutputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
<TechnicalProfile Id="LocalAccountDiscoveryUsingUserNameAndValidateStrongAuthenticationEmailAddress">
<DisplayName>Reset password using username</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<!-- Other values maybe defined in localized api.selfasserted.fr and api.selfasserted.en -->
<Item Key="IpAddressClaimReferenceId">IpAddress</Item>
<Item Key="ContentDefinitionReferenceId">api.localaccountpasswordreset</Item>
<Item Key="AllowGenerationOfClaimsWithNullValues">true</Item>
<Item Key="UserMessageIfClaimsTransformationStringsAreNotEqual">An account could not be found for the provided User ID and email combination.</Item>
<Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Your account has been locked. Contact your support person to unlock it, then try again.</Item>
<Item Key="LocalAccountType">Username</Item>
<Item Key="LocalAccountProfile">true</Item>
<!-- Reduce the default self-asserted retry limit of 7 for the reset journey -->
<Item Key="setting.retryLimit">5</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CopySignInEmailAddressToEmail" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="readOnlySignInName" />
<InputClaim ClaimTypeReferenceId="emailFromAAD" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="readOnlySignInName" Required="true" />
<OutputClaim ClaimTypeReferenceId="emailFromAAD" PartnerClaimType="Verified.Email" Required="true" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingUserNameAndValidateStrongAuthenticationEmailAddress" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>

Password Reset link Azure B2C Custom Policy

I have a custom policy that is working fine. However, noticed that the Password Reset (Forgotten Password) link is not showing. How do I get this to show, as we are also working on a custom ui for the policy as well.
I'm using the self asserted method.
<ContentDefinition Id="api.selfasserted">
<LoadUri>{Settings:CustomUIBaseUrl}/selfAsserted.html</LoadUri>
<RecoveryUri>~/common/default_page_error.html</RecoveryUri>
<DataUri>urn:com:microsoft:aad:b2c:elements:contract:selfasserted:2.1.0</DataUri>
<Metadata>
<Item Key="DisplayName">Collect information from user page</Item>
</Metadata>
</ContentDefinition>
that's being referenced by this technical profile
<DisplayName>Local Account Signin</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="DisplayName">Signin</Item>
<Item Key="SignUpTarget">SignUpWithLogonEmailExchange</Item>
<Item Key="setting.operatingMode">Email</Item>
<Item Key="setting.forgotPasswordLinkLocation">AfterLabel</Item>
<Item Key="ContentDefinitionReferenceId">api.selfasserted</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInName" Required="true" />
<OutputClaim ClaimTypeReferenceId="password" Required="true" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="login-NonInteractive" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
Use DATAURI: urn:com:microsoft:aad:b2c:elements:contract:unifiedssp:1.2.0 to have SignIn, SignUp and ForgotPassword in a single page.
When you click on Forgot Password you will get AADB2C90118 error code.
Handle Using Custom Policy:
https://github.com/azure-ad-b2c/samples/tree/master/policies/embedded-password-reset
If Using .Net then you can refer this link to handle the error code:
https://github.com/AzureADQuickStarts/B2C-WebApp-OpenIDConnect-DotNet-SUSI

Calling Rest Api from Custom Policy

I'm having problems calling a rest api from a custom policy. I need the data sent into the rest api like the following
{
"correlationId": "123456",
"message": {
"email": "test#somedomain.com"
}
}
I have the following Claims Transformation
<ClaimsTransformation Id="GenerateGetAadRequestBody" TransformationMethod="GenerateJson">
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" TransformationClaimType="message.emailAddress" />
<InputClaim ClaimTypeReferenceId="correlationId" TransformationClaimType="correlationId" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="getAadRequestBody" TransformationClaimType="outputClaim"/>
</OutputClaims>
</ClaimsTransformation>
And I'm making the Rest Api call using the following Claims Provider
<ClaimsProvider>
<DisplayName>Custom REST API</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="RestApiGetAad">
<DisplayName>Call the Rest API</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ServiceUrl">{Settings:RestApiGetAadUrl}</Item>
<Item Key="AuthenticationType">Basic</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="ClaimUsedForRequestPayload">getAadRequestBody</Item>
<Item Key="ResolveJsonPathsInJsonTokens">true</Item>
<Item Key="DebugMode">{Settings:RestDebugMode}</Item>
<Item Key="DefaultUserMessageIfRequestFailed">Cannot process your request right now, please try again later.</Item>
</Metadata>
<CryptographicKeys>
<Key Id="BasicAuthenticationUsername" StorageReferenceId="{Settings:RestApiUserName}" />
<Key Id="BasicAuthenticationPassword" StorageReferenceId="{Settings:RestApiPassword}" />
</CryptographicKeys>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="GenerateGetAadRequestBody" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="getAadRequestBody" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="useAAD" PartnerClaimType="content.UseAad" Required="true" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
It looks like the call is being made, however, there is nothing being sent in the body. Any idea what I'm doing wrong?
Unfortunatly, this was issuing the wrong error code. Should have been PEBCAK, as I did not have the claims that I was trying to use in the ClaimsTransformation. Think that posting here gave me a bit of Rubber Duck Debugging, and I walked the flow again spotting the issue

Custom Email verification in a single orchestration step in AD B2C Custom policies

I have a multi-step custom policy that first collects email from user and sends a verification code to the user when user clicks continue. The journey works fine. But the thing is validation of code is happening in next step. I need to bring that code validation in to the first orchestration step. I'm following the below doc to implement this journey:
"https://github.com/yoelhor/aadb2c-verification-code"
My technical profile is like the following:
<TechnicalProfile Id="LocalAccountSignUpWithLogonEmail-FirstStep">
<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>
<Item Key="language.button_continue">Send verification email</Item>
<Item Key="EnforceEmailVerification">False</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
<OutputClaim ClaimTypeReferenceId="verificationCode" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CopyEmailAsReadOnly" />
</OutputClaimsTransformations>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="REST-API-SendVerificationEmail" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
and the validation tech profile as follows:
<TechnicalProfile Id="REST-API-SendVerificationEmail">
<DisplayName>Sign-Up send link</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://myweb.azurewebsites.net/api/Identity/SendVerificationCode</Item>
<Item Key="AuthenticationType">None</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="AllowInsecureAuthInProduction">true</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="verificationCode" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
It cant be achieved currently, to call the REST API, the form must be submitted and that will cause the orchestration step to complete and move to the next one.
In a few weeks we will release how to achieve this, and without the use of an external REST API to generate and verify the OTP Codes. Stay tuned.

How to validate entered value is same as pre-defined value?

I am trying to verify that an email verification code entered by a user is same as one returned earlier by another technical profile. This journey step works as expected when the user enters the correct code but hangs when the user enters an incorrect code. What am I doing wrong. Here is the technical policy executed by my journey step:
<TechnicalProfile Id="EnterEmailVerifyCode">
<DisplayName>Email entry</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>
<Item Key="language.button_continue">Continue</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
<InputClaim ClaimTypeReferenceId="emailVerificationCodeEntered" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="emailVerificationCodeEntered" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="AssertEmailVerificationCodesAreEqual" />
</OutputClaimsTransformations>
</TechnicalProfile>
The self-asserted technical profile must invoke the claims transformation as a validation technical profile.
Declare a claims transformation technical profile that invokes the claims transformation:
<TechnicalProfile Id="ValidateEmailVerificationCodes">
<DisplayName>Validate Email Verification Codes</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<InputClaims>
<InputClaim ClaimTypeReferenceId="emailVerificationCodeEntered" />
<InputClaim ClaimTypeReferenceId="emailVerificationCodeGenerated" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="emailVerificationCodeEntered" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="AssertEmailVerificationCodesAreEqual" />
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
Then invoke this claims transformation technical profile from the self-asserted technical profile as a validation technical profile:
<TechnicalProfile Id="EnterEmailVerifyCode">
...
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
...
<Item Key="UserMessageIfClaimsTransformationStringsAreNotEqual">The verification code is invalid</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
<InputClaim ClaimTypeReferenceId="emailVerificationCodeEntered" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="emailVerificationCodeEntered" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="ValidateEmailVerificationCodes" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
UserMessageIfClaimsTransformationStringsAreNotEqual specifies the error message that is shown if the claims transformation raises errors.

Resources