Detailed Help Needed for Azure AD B2C JIT Migration Password Reset Flow: Claims Transformation Issue
Context and Use Case
I’m setting up an Azure AD B2C custom policy for a Just-In-Time (JIT) migration password reset scenario, and I’m encountering persistent issues that I cannot resolve. My scenario is as follows:
- A user from a legacy Identity Provider (IdP) tries to reset their password.
- The user does not yet exist in Azure AD B2C.
- The expected flow:
- The user clicks "Forgot Password" on the sign-in screen.
- The user enters their email and verifies it.
- If the user exists in the legacy IdP (but not in B2C), they are prompted to reset their password and are migrated to B2C.
However, I consistently encounter an error during the validation step when trying to determine if the accountEnabled
claim exists. Below, I’ve detailed the steps I’ve followed, configurations, and the issues I’ve encountered, along with relevant logs and snippets.
Issue Details
The following error is observed in the b2c sign in screen after clicking forgot my password, verifying my email, and trying to click continue:
Boolean claim value comparison failed for claim type "inputClaim".
The following error is captured in Application Insights when the user attempts to verify their email and continue:
Relevant Application Insights JSON:
{
"ErrorCodes": ["UserMessageIfClaimsTransformationBooleanValueIsNotEqual"],
"ValidationTechnicalProfiles": ["CheckAccountEnabledExists", "AAD-UserReadUsingEmailAddress"],
"Exception": {
"Message": "ErrorCodes: UserMessageIfClaimsTransformationBooleanValueIsNotEqual",
"Data": {
"IsPolicySpecificError": false
}
}
}
Additionally, I’ve previously encountered this error:
A Claim of ClaimType with id "accountEnabled" was not found, which is required by the ClaimsTransformationImpl of Type "Microsoft.Cpim.Data.Transformations.AssertBooleanClaimIsEqualToValueTransformation" for TransformationMethod "AssertBooleanClaimIsEqualToValue".
My Setup
Relevant User Journey
<UserJourneys>
<UserJourney Id="SignInWithMigration">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
<ClaimsProviderSelection TargetClaimsExchangeId="ForgotPasswordExchange" />
<ClaimsProviderSelection TargetClaimsExchangeId="FacebookExchange" />
<ClaimsProviderSelection TargetClaimsExchangeId="GoogleExchange" />
</ClaimsProviderSelections>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail" />
<ClaimsExchange Id="ForgotPasswordExchange" TechnicalProfileReferenceId="ForgotPassword" />
<ClaimsExchange Id="FacebookExchange" TechnicalProfileReferenceId="Facebook-OAUTH" />
<ClaimsExchange Id="GoogleExchange" TechnicalProfileReferenceId="Google-OAuth2" />
<!-- <ClaimsExchange Id="FacebookorGoogleExchange" TechnicalProfileReferenceId="SocialAccountUserExists" />
<ClaimsExchange Id="SocialAccountSignInExchange" TechnicalProfileReferenceId="SocialAccountSignIn" /> -->
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="InvokeSubJourney">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>isForgotPassword</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<JourneyList>
<Candidate SubJourneyReferenceId="PasswordResetSub" />
</JourneyList>
</OrchestrationStep>
Sub-Journey for Password Reset
<SubJourneys>
<SubJourney Id="PasswordResetSub" Type="Call">
<OrchestrationSteps>
<!-- Step 1: Discover if the user exists in B2C -->
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="PasswordResetUsingEmailAddressExchange" TechnicalProfileReferenceId="LocalAccountDiscoveryUsingEmailAddress" />
</ClaimsExchanges>
</OrchestrationStep>
Technical Profiles
- Parent Profile:
LocalAccountDiscoveryUsingEmailAddress
<TechnicalProfile Id="LocalAccountDiscoveryUsingEmailAddress">
<DisplayName>Reset password using email address</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.localaccountpasswordreset</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<IncludeInSso>false</IncludeInSso>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
<OutputClaim ClaimTypeReferenceId="accountEnabledExists" />
<OutputClaim ClaimTypeReferenceId="debugMessage" DefaultValue="N/A" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="CheckAccountEnabledExists" />
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingEmailAddress" >
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="false">
<Value>accountEnabledExists</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
</ValidationTechnicalProfiles>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
- Validation Profile:
CheckAccountEnabledExists
<TechnicalProfile Id="CheckAccountEnabledExists">
<DisplayName>Check if accountEnabled claim exists</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine" />
<!-- <InputClaims>
<InputClaim ClaimTypeReferenceId="accountEnabled" />
</InputClaims> -->
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="accountEnabled" DefaultValue="false" />
<OutputClaim ClaimTypeReferenceId="accountEnabledExists" />
<OutputClaim ClaimTypeReferenceId="debugMessage" DefaultValue="Debug: AccountEnabledExists Value = {accountEnabledExists}" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CheckIfAccountEnabledExists" />
</OutputClaimsTransformations>
</TechnicalProfile>
- Claims Transformation:
CheckIfAccountEnabledExists
<ClaimsTransformation Id="CheckIfAccountEnabledExists" TransformationMethod="DoesClaimExist">
<InputClaims>
<InputClaim ClaimTypeReferenceId="accountEnabled" TransformationClaimType="inputClaim" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="accountEnabledExists" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
- Validation Profile:
AAD-UserReadUsingEmailAddress
<TechnicalProfile Id="AAD-UserReadUsingEmailAddress">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress" Required="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="accountEnabled" />
<OutputClaim ClaimTypeReferenceId="otherMails" />
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="AssertAccountEnabledIsTrue" />
</OutputClaimsTransformations>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
What I’ve Tried
Added Default Values: Added DefaultValue="false"
for accountEnabled
and accountEnabledExists
to avoid null issues.
- Introduced Precondition: Attempted to skip
AAD-UserReadUsingEmailAddress
by introducing the precondition:
Checked Claims Bag: Used<Precondition Type="ClaimEquals" ExecuteActionsIf="false">
CheckAccountEnabledExists
to confirm ifaccountEnabled
exists and output its result intoaccountEnabledExists
. Tried to Debug: Enabled App Insights logging and trackedaccountEnabledExists
but still receive the error during theAssertBooleanClaimIsEqualToValue
transformation.
Documentation Followed
I followed Microsoft’s documentation to set up the password reset flow. Specifically:
- Step 3 invokes the
PasswordResetSub
sub-journey. - The sub-journey calls the
LocalAccountDiscoveryUsingEmailAddress
technical profile.
Help Needed
I’m seeking guidance on the following:
Why does the AssertBooleanClaimIsEqualToValue
transformation fail with accountEnabled
even though CheckIfAccountEnabledExists
should skip AAD-UserReadUsingEmailAddress
if the claim is missing?
How can I ensure that accountEnabledExists
correctly reflects the presence of accountEnabled
in the claims bag, and the policy handles this gracefully when the user is not found in B2C?
Any assistance or insights would be greatly appreciated!