Displaying the error message with an Azure AD B2C custom policy for password reset - azure-ad-b2c

I have followed the steps in this SO question and also the AssertBooleanClaimIsEqualToValue documentation.
However, I cannot get the error message to display.
I have an email link for a custom password reset flow. The link validates the user so I don't need the user to validate the email address as per the normal password reset flow.
When the user clicks the link, I set a custom attribute so that the user cannot click the link twice. In this case I want to display an error message.
<UserJourney Id="PasswordReset-Custom">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="PasswordResetUsingEmailAddress" TechnicalProfileReferenceId="AAD-UserReadUsingEmailAddress" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="NewCredentials" TechnicalProfileReferenceId="LocalAccountWritePasswordUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="UpdateFlag" TechnicalProfileReferenceId="AAD-UserWriteProfileUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="4" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
The AAD-UserWriteProfileUsingObjectId writes a flag to indicate the journey has been done.
<TechnicalProfile Id="LocalAccountWritePasswordUsingObjectId">
<DisplayName>Change password (username)</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.localaccountpasswordreset</Item>
<Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Process complete blah.</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="newPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserWritePasswordUsingObjectId" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
This has a validation TP AAD-UserWritePasswordUsingObjectId.
<TechnicalProfile Id="AAD-UserWritePasswordUsingObjectId">
<Metadata>
<Item Key="Operation">Write</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true" />
</InputClaims>
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="objectId" />
<PersistedClaim ClaimTypeReferenceId="newPassword" PartnerClaimType="password"/>
</PersistedClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="dummyObjectId" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="EnsureCompletedIsTrue" />
</OutputClaimsTransformations>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
This has an OutputClaimsTransformation EnsureCompletedIsTrue.
<ClaimsTransformation Id="EnsureCompletedIsTrue" TransformationMethod="AssertBooleanClaimIsEqualToValue">
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_Completed" TransformationClaimType="inputClaim" />
</InputClaims>
<InputParameters>
<InputParameter Id="valueToCompareTo" DataType="boolean" Value="true" />
</InputParameters>
</ClaimsTransformation>
So if extension_Completed is True it should throw the error message "Process complete".
I have checked that the flag is true but I never see the error message?

According to the doc, it assert the claims value is as expected, otherwise throw error. You are asserting it against a "true" but shouldn't it be against "false"?
If the link has never been used, the claim value is false. The validation is done to assert that is false. Once it's being used, the claim value should be true (I believe you'd handle this somewhere). And if the user use the link agains, the claim value now is true, and the assertion would throw because it expects a false value.
<ClaimsTransformation Id="EnsureCompletedIsTrue" TransformationMethod="AssertBooleanClaimIsEqualToValue">
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_Completed" TransformationClaimType="inputClaim" />
</InputClaims>
<InputParameters>
<InputParameter Id="valueToCompareTo" DataType="boolean" Value="false" />
</InputParameters>
</ClaimsTransformation>

Related

Azure B2C - change sign in - token returns old email address

I have taken the change sign in example (https://github.com/azure-ad-b2c/samples/tree/master/policies/change-sign-in-name) and added this into my custom policy files. If I run through this process and change the user's email address, then the value in B2C's AD is updated to the new email address, however the value in the returned token is still the user's old email address before they updated it.
However I want to return the new email address that the user entered back to the application. Is this possible?
My code:
Relying Party:
<RelyingParty>
<DefaultUserJourney ReferenceId="ChangeSignInName" />
<UserJourneyBehaviors>
<ScriptExecution>Allow</ScriptExecution>
</UserJourneyBehaviors>
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" />
<OutputClaim ClaimTypeReferenceId="objectId"/>
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub"/>
<OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
User Journey:
<UserJourney Id="ChangeSignInName">
<OrchestrationSteps>
<!-- To change the sign-in names, user need to sign-in with local account only -->
<OrchestrationStep Order="1" Type="ClaimsProviderSelection" ContentDefinitionReferenceId="api.selfasserted.changesignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="LocalAccountSigninEmailExchange" />
</ClaimsProviderSelections>
</OrchestrationStep>
<!-- Sign-in user with local account-->
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="LocalAccountDiscoveryUsingEmailAddress2" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Read the user data from the Azure Active Directory -->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>socialIdpAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- This self asserted technical profile validates and update the sign-in name -->
<OrchestrationStep Order="4" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="B2CUserSignInUpdateExchange" TechnicalProfileReferenceId="SelfAsserted-EmailUpdate" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="5" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
Technical Profiles:
<TechnicalProfile Id="LocalAccountDiscoveryUsingEmailAddress2">
<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.selfasserted.changesignin</Item>
<Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Your account has been locked. Contact your support person to unlock it, then try again.</Item>
<!--OTP validation error messages-->
<Item Key="UserMessageIfSessionDoesNotExist">You have exceed the maximum time allowed.</Item>
<Item Key="UserMessageIfMaxRetryAttempted">You have exceed the number of retries allowed.</Item>
<Item Key="UserMessageIfInvalidCode">You have entered the wrong code.</Item>
<Item Key="UserMessageIfSessionConflict">Cannot verify the code, please try again later.</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<IncludeInSso>false</IncludeInSso>
<DisplayClaims>
<DisplayClaim DisplayControlReferenceId="emailVerificationControl" />
</DisplayClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="email" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingEmailAddress" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
<TechnicalProfile Id="AAD-UserReadUsingObjectId">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true" />
</InputClaims>
<OutputClaims>
<!-- Optional claims -->
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="otherMails" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
<TechnicalProfile Id="SelfAsserted-EmailUpdate">
<DisplayName>Email update</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.changesignin</Item>
<!--OTP validation error messages-->
<Item Key="UserMessageIfSessionDoesNotExist">You have exceed the maximum time allowed.</Item>
<Item Key="UserMessageIfMaxRetryAttempted">You have exceed the number of retries allowed.</Item>
<Item Key="UserMessageIfInvalidCode">You have entered the wrong code.</Item>
<Item Key="UserMessageIfSessionConflict">Cannot verify the code, please try again later.</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="alternativeSecurityId" />
<InputClaim ClaimTypeReferenceId="userPrincipalName" />
</InputClaims>
<DisplayClaims>
<DisplayClaim DisplayControlReferenceId="emailVerificationControlUpdateEmail" />
</DisplayClaims>
<OutputClaims>
<!-- Collect and validate the email address -->
<OutputClaim ClaimTypeReferenceId="executed-SelfAsserted-Input" DefaultValue="true" />
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
</OutputClaims>
<ValidationTechnicalProfiles>
<!-- Call validation technical profile to persist the data-->
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteEmailUsingObjectId" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
If Azure AD User's object is updated correctly then most likely updated claim is not added to the claim's bag. To make things easier, do this test first and report the findings:
Add a new step right before the last step, making it step 5 and the last step will be step 6 as shown below:
<OrchestrationStep Order="5" Type="ClaimsExchange">
<ClaimsExchange Id="AADUserReadWithObjectId_AfterUpdate" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="5" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
Run the policy, and confirm whether you do see an updated email or an old one? if you see the updated email then it means one of the technical profiles not emitting the output claim correctly.

Persisting strongAuthenticationEmailAddress, resets strongAuthenticationPhoneNumber and vice-versa in Azure B2C custom policy

I was able to edit the ‘strongAuthenticationEmailAddress’ by modifying the 'ProfileEditWithUsername' user journey defined in the extension file of the policy(https://github.com/azure-ad-b2c/samples/tree/master/policies/username-signup-or-signin). I persisted the strongAuthenticationEmailAddress in the 'AAD-UserWriteProfileUsingObjectId' technical profile which was used by the above user journey as a validation profile in Orchestration Step 4.
However, I noticed that if I run the policy to change the strongAuthenticationEmailAddress, the email is changed successfully but the strongAuthenticationPhoneNumber & Alternate phone(used for authentication) is being set to blank.
Similarly I implemented the edit-MFA phone number policy (https://github.com/azure-ad-b2c/samples/tree/master/policies/edit-mfa-phone-number), and using this I am able to edit the strongAuthenticationPhoneNumber, but it sets the strongAuthenticationEmailAddress to blank.
<UserJourney Id="ProfileEditWithUsername">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsProviderSelection" ContentDefinitionReferenceId="api.idpselections">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="LocalAccountSigninUsernameExchange" />
</ClaimsProviderSelections>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninUsernameExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Username" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="4" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="B2CUserProfileUpdateExchange" TechnicalProfileReferenceId="SelfAsserted-ProfileUpdate" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="5" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
--------------------------------------------------
<TechnicalProfile Id="AAD-UserReadUsingObjectId">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true" />
</InputClaims>
<OutputClaims>
<!-- Required claims -->
<OutputClaim ClaimTypeReferenceId="strongAuthenticationPhoneNumber" />
<OutputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" />
<!-- Optional claims -->
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="otherMails" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surname" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
-------------------------------------------------
<TechnicalProfile Id="SelfAsserted-ProfileUpdate">
<DisplayName>User ID signup</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.profileupdate</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="alternativeSecurityId" />
<InputClaim ClaimTypeReferenceId="userPrincipalName" />
<!-- Optional claims. These claims are collected from the user and can be modified. Any claim added here should be updated in the
ValidationTechnicalProfile referenced below so it can be written to directory after being updateed by the user, i.e. AAD-UserWriteProfileUsingObjectId. -->
<InputClaim ClaimTypeReferenceId="givenName" />
<InputClaim ClaimTypeReferenceId="surname" />
<InputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" />
</InputClaims>
<OutputClaims>
<!-- Required claims -->
<OutputClaim ClaimTypeReferenceId="executed-SelfAsserted-Input" DefaultValue="true" />
<!-- Optional claims. These claims are collected from the user and can be modified. Any claim added here should be updated in the
ValidationTechnicalProfile referenced below so it can be written to directory after being updateed by the user, i.e. AAD-UserWriteProfileUsingObjectId. -->
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surname" />
<OutputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteProfileUsingObjectId" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
---------------------------------------------------------------------------
<TechnicalProfile Id="AAD-UserWriteProfileUsingObjectId">
<Metadata>
<Item Key="Operation">Write</Item>
<Item Key="RaiseErrorIfClaimsPrincipalAlreadyExists">false</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true" />
</InputClaims>
<PersistedClaims>
<!-- Required claims -->
<PersistedClaim ClaimTypeReferenceId="objectId" />
<!-- Optional claims -->
<PersistedClaim ClaimTypeReferenceId="givenName" />
<PersistedClaim ClaimTypeReferenceId="surname" />
<PersistedClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" />
</PersistedClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
I remember going through the same frustrating problem.
If you persist one of the strongAuthentication fields within a TechnicalProfile, it will wipe out any of the other strongAuthentication fields unless you also persist those.
So wherever you are persisting one strongAuthentication field, persist them all instead. In your example, your technical profile "AAD-UserWriteProfileUsingObjectId" should look something like this:
<TechnicalProfile Id="AAD-UserWriteProfileUsingObjectId">
<Metadata>
<Item Key="Operation">Write</Item>
<Item Key="RaiseErrorIfClaimsPrincipalAlreadyExists">false</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true" />
</InputClaims>
<PersistedClaims>
<!-- Required claims -->
<PersistedClaim ClaimTypeReferenceId="objectId" />
<!-- Optional claims -->
<PersistedClaim ClaimTypeReferenceId="givenName" />
<PersistedClaim ClaimTypeReferenceId="surname" />
<PersistedClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" />
<!-- *** NEW LINE *** -->
<PersistedClaim ClaimTypeReferenceId="strongAuthenticationPhoneNumber" />
</PersistedClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
Note the new line added in the PersistedClaims for strongAuthenticationPhoneNumber.
There is another place in the schema this occurs and it's with the signInNames collection. If you persist something like signInNames.username it will wipe out any other signInNames (like a signInNames.emailaddress) unless you also persist the signInNames.emailaddress, so just be aware of that too.

Terms of Service pops up on Sign-In although already agreed

I am using the Single-Page Application built on MSAL.js with Azure AD B2C together with the A B2C IEF Custom Policy - Sign Up and Sign In with 'Terms of Use' prompt and a custom RestAPI profile to enrich the output claims.
Everything seems fine until I press the "Call API" button of the application, which prompts the "I agree to the Terms of Service" popup:
This should not be happening, as the latest ToS were agreed upon signup. The same happens if I logout and login again. So somehow aquiring the access token prompts the same popup.
I believe that this prompt should be displayed only on signin anyway (i.e. when acquiring an id token), so something is not right with my configuration.
Btw, the id token looks fine to me:
How could I fix this?
This is the custom policy that I am using:
<?xml version="1.0" encoding="utf-8" ?>
<TrustFrameworkPolicy xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06" PolicySchemaVersion="0.3.0.0" TenantId="tenant.onmicrosoft.com" PolicyId="B2C_1A_TOU_SUSI" PublicPolicyUri="http://tenant.onmicrosoft.com/B2C_1A_TOU_SUSI">
<BasePolicy>
<TenantId>tenant.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId>
</BasePolicy>
<BuildingBlocks>
<ClaimsSchema>
<ClaimType Id="extension_termsOfUseConsentChoice">
<DisplayName></DisplayName>
<DataType>string</DataType>
<UserInputType>CheckboxMultiSelect</UserInputType>
<Restriction>
<Enumeration Text=" I agree to the Terms Of Service" Value="AgreeToTermsOfUseConsentYes" SelectByDefault="false" />
</Restriction>
</ClaimType>
<ClaimType Id="currentTime">
<DisplayName>Current Time</DisplayName>
<DataType>dateTime</DataType>
<AdminHelpText>Current date time in UTC.</AdminHelpText>
<UserHelpText>Current date time in UTC.</UserHelpText>
</ClaimType>
<ClaimType Id="extension_termsOfUseConsentDateTime">
<DisplayName>Terms of Use Consent Date Time</DisplayName>
<DataType>dateTime</DataType>
<AdminHelpText>Terms of Use Consent date time in UTC.</AdminHelpText>
<UserHelpText>Terms of Use Consent date time in UTC.</UserHelpText>
</ClaimType>
<ClaimType Id="termsOfUseConsentRequired">
<DisplayName>Terms of Use Consent Required</DisplayName>
<DataType>boolean</DataType>
<AdminHelpText>Boolean that specifies if Terms of Use Consent is required or not.</AdminHelpText>
<UserHelpText>Boolean that specifies if Terms of Use Consent is required or not</UserHelpText>
</ClaimType>
<ClaimType Id="extension_termsOfUseConsentVersion">
<DisplayName>Terms of Use Consent Version</DisplayName>
<DataType>string</DataType>
<AdminHelpText>Terms of Use Consent Version.</AdminHelpText>
<UserHelpText>Terms of Use Consent Version.</UserHelpText>
</ClaimType>
<ClaimType Id="groupIds">
<DisplayName>Your Group Ids</DisplayName>
<DataType>stringCollection</DataType>
</ClaimType>
</ClaimsSchema>
<ClaimsTransformations>
<ClaimsTransformation Id="GetCurrentDateTime" TransformationMethod="GetCurrentDateTime">
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="currentTime" TransformationClaimType="currentDateTime" />
</OutputClaims>
</ClaimsTransformation>
<ClaimsTransformation Id="IsTermsOfUseConsentRequiredForDateTime" TransformationMethod="IsTermsOfUseConsentRequired">
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_termsOfUseConsentDateTime" TransformationClaimType="termsOfUseConsentDateTime" />
</InputClaims>
<InputParameters>
<InputParameter Id="termsOfUseTextUpdateDateTime" DataType="dateTime" Value="2020-01-30T23:03:45" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="termsOfUseConsentRequired" TransformationClaimType="result" />
</OutputClaims>
</ClaimsTransformation>
<ClaimsTransformation Id="GetNewUserAgreeToTermsOfUseConsentVersion" TransformationMethod="CreateStringClaim">
<InputParameters>
<InputParameter Id="value" DataType="string" Value="V1"/>
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="extension_termsOfUseConsentVersion" TransformationClaimType="createdClaim" />
</OutputClaims>
</ClaimsTransformation>
<!-- Not used here, but this OR IsTermsOfUseConsentRequiredForDateTime check can be used -->
<ClaimsTransformation Id="IsTermsOfUseConsentRequiredForVersion" TransformationMethod="CompareClaimToValue">
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_termsOfUseConsentVersion" TransformationClaimType="inputClaim1" />
</InputClaims>
<InputParameters>
<InputParameter Id="compareTo" DataType="string" Value="V2" />
<InputParameter Id="operator" DataType="string" Value="not equal" />
<InputParameter Id="ignoreCase" DataType="string" Value="true" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="termsOfUseConsentRequired" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
</ClaimsTransformations>
</BuildingBlocks>
<ClaimsProviders>
<ClaimsProvider>
<!-- Page that prompts for a new TOU on sign in if the users TOU is out of date-->
<DisplayName>Self Asserted</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="SelfAsserted-Input-ToU-SignIn">
<DisplayName>Self Asserted ToU</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="AllowGenerationOfClaimsWithNullValues">true</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" />
<InputClaim ClaimTypeReferenceId="extension_termsOfUseConsentChoice" DefaultValue="AgreeToTermsOfUseConsentNo" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="extension_termsOfUseConsentChoice" Required="true" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="Update-TOU-Status" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Local Account</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Email">
<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="SignUpTarget">SignUpWithLogonEmailExchange</Item>
<Item Key="setting.operatingMode">Email</Item>
<Item Key="ContentDefinitionReferenceId">api.selfasserted</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" />
<InputClaim ClaimTypeReferenceId="currentTime" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInName" Required="true" />
<OutputClaim ClaimTypeReferenceId="password" Required="true" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
<OutputClaim ClaimTypeReferenceId="termsOfUseConsentRequired"/>
<OutputClaim ClaimTypeReferenceId="extension_termsOfUseConsentDateTime" />
<OutputClaim ClaimTypeReferenceId="groupIds" DefaultValue="[]" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="GetCurrentDateTime" />
</OutputClaimsTransformations>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="login-NonInteractive" />
<ValidationTechnicalProfile ReferenceId="Check-TOU-Status" />
<ValidationTechnicalProfile ReferenceId="REST-GetProfile" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
<!-- read the last time the user consented -->
<TechnicalProfile Id="Check-TOU-Status">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="extension_termsOfUseConsentDateTime" DefaultValue="2017-10-01T15:00:00.0000000Z" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="IsTermsOfUseConsentRequiredForDateTime" />
</OutputClaimsTransformations>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
<!-- write the current time to the consent attribute if the user reconsents to TOU on sign in -->
<TechnicalProfile Id="Update-TOU-Status">
<Metadata>
<Item Key="Operation">Write</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true" />
</InputClaims>
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="objectId" />
<PersistedClaim ClaimTypeReferenceId="currentTime" PartnerClaimType="extension_termsOfUseConsentDateTime" />
</PersistedClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
<!-- sign up page with TOU checkbox-->
<TechnicalProfile Id="LocalAccountSignUpWithLogonEmailCustom">
<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="ContentDefinitionReferenceId">api.localaccountsignup</Item>
<Item Key="language.button_continue">Create</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="GetCurrentDateTime" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_termsOfUseConsentChoice" DefaultValue="AgreeToTermsOfUseConsentNo" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="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" /> -->
<OutputClaim ClaimTypeReferenceId="extension_termsOfUseConsentChoice" Required="true" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonEmail" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
<!-- During sign up, write the current time of consent, and version -->
<TechnicalProfile Id="AAD-UserWriteUsingLogonEmail">
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="currentTime" PartnerClaimType="extension_termsOfUseConsentDateTime"/>
<PersistedClaim ClaimTypeReferenceId="extension_termsOfUseConsentChoice" />
<PersistedClaim ClaimTypeReferenceId="extension_termsOfUseConsentVersion" DefaultValue="V1" />
</PersistedClaims>
</TechnicalProfile>
<TechnicalProfile Id="REST-GetProfile">
<DisplayName>Get user extended profile via a REST API call</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://<myserveraddress>/claims/GetGroupIds</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="AuthenticationType">Basic</Item>
<Item Key="AllowInsecureAuthInProduction">false</Item>
</Metadata>
<CryptographicKeys>
<Key Id="BasicAuthenticationUsername" StorageReferenceId="B2C_1A_RestApiUsername" />
<Key Id="BasicAuthenticationPassword" StorageReferenceId="B2C_1A_RestApiPassword" />
</CryptographicKeys>
<OutputClaims>
<!-- Claims parsed from your REST API -->
<OutputClaim ClaimTypeReferenceId="groupIds" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
<TechnicalProfile Id="AAD-Common">
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.AzureActiveDirectoryProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ApplicationObjectId"><MyApplicationObjectId></Item>
<Item Key="ClientId"><MyClientId></Item>
</Metadata>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<UserJourneys>
<UserJourney Id="B2CSignUpOrSignInWithPasswordToU" NonInteractive="false">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
</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="LocalAccountSignUp" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmailCustom" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- This step reads any user attributes that we may not have received when authenticating using ESTS so they can be sent
in the token. -->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<Preconditions>
<!-- Add condition to not execute this step for sign up scenario based on newUser claim -->
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>newUser</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Display Terms of Use consent page for any SignIn scenario based on termsOfUseConsentRequired claim -->
<OrchestrationStep Order="4" Type="ClaimsExchange">
<Preconditions>
<!-- Add condition to not execute this step for sign up scenario based on newUser claim -->
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>newUser</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
<Precondition Type="ClaimEquals" ExecuteActionsIf="false">
<Value>termsOfUseConsentRequired</Value>
<Value>True</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="ShowToUConsentPageForNewUser" TechnicalProfileReferenceId="SelfAsserted-Input-ToU-SignIn" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="5" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="RESTGetProfile" TechnicalProfileReferenceId="REST-GetProfile" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="6" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
</UserJourneys>
<RelyingParty>
<DefaultUserJourney ReferenceId="B2CSignUpOrSignInWithPasswordToU" />
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="newUser" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" />
<OutputClaim ClaimTypeReferenceId="extension_termsOfUseConsentDateTime" />
<OutputClaim ClaimTypeReferenceId="currentTime" />
<OutputClaim ClaimTypeReferenceId="email" />
<OutputClaim ClaimTypeReferenceId="groupIds" DefaultValue="[]" />
<OutputClaim ClaimTypeReferenceId="termsOfUseConsentRequired" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
</TrustFrameworkPolicy>
Can you add
<TechnicalProfile Id="SM-AAD">
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="termsOfUseConsentRequired" />
</PersistedClaims>
</TechnicalProfile>
It is because thistermsOfUseConsentRequired claim is not being evaluated when running the policy with prompt=none (SSO/silent token call within SPA).

Azure Active Directory B2C Custom Invite Policy - Passing Custom Claims Between Steps

I have implemented an invite policy (invite users to the site by sending them an email link) via this example https://github.com/azure-ad-b2c/samples/tree/master/policies/invite
I have this user journey
<UserJourney Id="SignUpInvitation">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="GetClaims" CpimIssuerTechnicalProfileReferenceId="IdTokenHint_ExtractClaims" />
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>email</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SelfAsserted-Unsolicited" TechnicalProfileReferenceId="SelfAsserted-Unsolicited"/>
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSignUpWithReadOnlyEmail" TechnicalProfileReferenceId="LocalAccountSignUpWithReadOnlyEmail"/>
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="4" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="5" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer"/>
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb"/>
</UserJourney>
In the first step, I extract the claims from the JWT. An email and a custom claim,
<TechnicalProfiles>
<TechnicalProfile Id="IdTokenHint_ExtractClaims">
<DisplayName> My ID Token Hint TechnicalProfile</DisplayName>
<Protocol Name="None" />
<Metadata>
<Item Key="METADATA">https://mywebsite.com/internal/v1/invitation/.well-known/openid-configuration</Item>
</Metadata>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" />
<OutputClaim ClaimTypeReferenceId="extension_DBId" DefaultValue="1" />
</OutputClaims>
</TechnicalProfile>
</TechnicalProfiles>
(Note that I set a default value of 1 to my custom claim)
Then, I have the technical profile that writes the user to the directory
<TechnicalProfile Id="LocalAccountSignUpWithReadOnlyEmail">
<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">Create</Item>
<!-- Sample: Remove sign-up email verification -->
<Item Key="EnforceEmailVerification">False</Item>
</Metadata>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CopyEmailAddress" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="ReadOnlyEmail" />
<InputClaim ClaimTypeReferenceId="extension_DBId" DefaultValue="2" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="ReadOnlyEmail" 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" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonEmail-Custom" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
(Note that here I set a default value of 2 to my custom claim)
AAD-UserWriteUsingLogonEmail-Custom is a modification of the built-in (inside TrustFrameworkBase.xml) AAD-UserWriteUsingLogonEmail profile, I've modified it to include my custom claim extension_DBId as a persisted claim:
<PersistedClaim ClaimTypeReferenceId="extension_DBId" DefaultValue="3" />
Here is the problem,
I generate a JWT with some arbitrary extension_DBId (Not 1,2 or 3)
But when I run the policy with it, in the output claims, I get extension_DBId=2
Which indicates that the third orchestration step (LocalAccountSignUpWithReadOnlyEmail) didn't receive the extension_DBId claim from the first orchestration step (IdTokenHint_ExtractClaims).
Why doesn't the IdTokenHint_ExtractClaims profile transfer the extension_DBId claim to the LocalAccountSignUpWithReadOnlyEmail profile?
I tried to remove the second step (SelfAsserted-Unsolicited), Still didn't work.
What could it be?
For a user journey to receive an incoming claim from the input JWT, you must add an <InputClaim /> to the relying party <TechnicalProfile />:
<RelyingParty>
<TechnicalProfile Id="PolicyProfile">
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
<InputClaim ClaimTypeReferenceId="extension_DBId" />
</InputClaims>
</TechnicalProfile>
</RelyingParty>
If the claim type of the internal claim (e.g. extension_DBId) is different to that of the input JWT (e.g. DBId), then you can add the PartnerClaimType attribute to the InputClaim element:
<InputClaim ClaimTypeReferenceId="extension_DBId" PartnerClaimType="DBId" />
#ChrisPadgett pointed out that I have to add my custom claim(s) (extension_DBId) to the InputClaims in relying party policy
<RelyingParty>
<DefaultUserJourney ReferenceId="SignUpInvitation" />
<UserJourneyBehaviors>
<ContentDefinitionParameters>
<Parameter Name="ui_locales">{Culture:RFC5646}</Parameter>
</ContentDefinitionParameters>
<ScriptExecution>Allow</ScriptExecution>
</UserJourneyBehaviors>
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
<InputClaim ClaimTypeReferenceId="DBId" /> <!-- This was added -->
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="emails" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub"/>
<OutputClaim ClaimTypeReferenceId="extension_DBId" />
<OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
(I then have a claim transformation that converts DBId to extension_DBId)

Email Address Auto Populate not working in Azure B2C Custom Policy and disable change email option

Continuing from Populate the email address text box in Azure AD B2C Orchestration something has gone wrong. The email box is not populating and I am missing something. Idea is to verify a login with Email OTP and then let then change/add Mobile number for MFA
UserJourney
<UserJourney Id="TestEmailOTP">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsProviderSelection" ContentDefinitionReferenceId="api.idpselections">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="LocalAccountSigninEmailExchange" />
</ClaimsProviderSelections>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="4" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="VerifyWithEmailOTP" TechnicalProfileReferenceId="LocalAccountDiscoveryUsingEmailAddressEmailOTP" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="5" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="NewPhoneFactor" TechnicalProfileReferenceId="PhoneFactor-EditAndVerify" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="6" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserWriteWithObjectId" TechnicalProfileReferenceId="AAD-UserWritePhoneNumberUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="7" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
Technical Profile
<TechnicalProfile Id="LocalAccountDiscoveryUsingEmailAddressEmailOTP">
<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="readonlyEmail" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingEmailAddress" />
<ValidationTechnicalProfile ReferenceId="SelfAsserted-LocalAccount-EmailVerification" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
AAD-UserReadUsingEmailAddress
<TechnicalProfile Id="AAD-UserReadUsingEmailAddress">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
<Item Key="UserMessageIfClaimsPrincipalDoesNotExist">An account could not be found for the provided user ID.</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames" Required="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="otherMails" />
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
Rest of the "SelfAsserted-LocalAccount-EmailVerification" is the same as previous post and the claims etc.
<TechnicalProfile Id="SelfAsserted-LocalAccount-EmailVerification">
<DisplayName>Local Account Email Address Verification</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.localaccount.emailverification</Item>
<Item Key="EnforceEmailVerification">true</Item>
</Metadata>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CreateReadonlyEmailClaim" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="readonlyEmail" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="readonlyEmail" PartnerClaimType="verified.email" Required="true" />
</OutputClaims>
</TechnicalProfile>
The CreateReadonlyEmailClaim claims transformation is defined as:
<ClaimsTransformation Id="CreateReadonlyEmailClaim" TransformationMethod="FormatStringClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" TransformationClaimType="inputClaim" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringFormat" DataType="string" Value="{0}" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="readonlyEmail" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
The readonlyEmail claim type is declared as:
<ClaimType Id="readonlyEmail">
<DisplayName>E-mail Address</DisplayName>
<DataType>string</DataType>
<UserInputType>Readonly</UserInputType>
</ClaimType>
What am I missing here?
I also tried this but get a 500 error after login
<TechnicalProfile Id="LocalAccountDiscoveryUsingEmailAddressEmailOTP">
<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>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CreateReadonlyEmailClaim" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="readonlyEmail" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="readonlyEmail" PartnerClaimType="verified.email" Required="true" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingEmailAddressEmailOTP" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
Also once email has been verified there is a change email option - we want that disabled or better - removed so that they can't do it after verification and moved to next step
As per #ChrisPadgett comment - The AAD-UserReadUsingObjectId TP returns an output claim of signInNames.emailAddress, not email, so the CreateReadonlyEmailClaim claims transformation should refer to an input claim of signInNames.emailAddress, not email.
Fixed by changing the transformation.

Resources