How to hide authorization code verification step in AD B2C custom policy? - azure-ad-b2c

Is it possible to create a custom policy to reset passwords at the member activation step without entering the activation code?
I am creating a user using Graph API and sending an invitation email to the specified email address with extension_activationCode claim as a token.
var emailClaim = new Claim("email", email);
var codeClaim = new Claim("extension_ActivationCode", activationCode);
policyClaims.Add(emailClaim);
policyClaims.Add(codeClaim);
I want the user to click on the link in that email and just set a password for his account. Currently, the UI is auto-populating the code and showing the Continue button so that the user can click it.
<UserJourney Id="MemberActivationDYP">
<PreserveOriginalAssertion>false</PreserveOriginalAssertion>
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsExchange" ContentDefinitionReferenceId="api.localaccount.activation">
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountPasswordRecoveryVerifiedEmailExchange" TechnicalProfileReferenceId="LocalAccount-ActivationDYP" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AAD-UserReadUsingObjectIdExchange" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<TechnicalProfile Id="LocalAccount-Activation">
<DisplayName>Account Activation</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.activation</Item>
<Item Key="IpAddressClaimReferenceId">IpAddress</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
<InputClaim ClaimTypeReferenceId="extension_activationCode" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" Required="true" />
<OutputClaim ClaimTypeReferenceId="extension_activationCode" Required="true" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="extension_isAccountActivated" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingEmailAddress" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
<TechnicalProfile Id="LocalAccount-ActivationDYP">
<DisplayName>Member Portal Account Activation</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.activation</Item>
<Item Key="IpAddressClaimReferenceId">IpAddress</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainerCRM" />
</CryptographicKeys>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
<InputClaim ClaimTypeReferenceId="extension_activationCode" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" Required="true" />
<OutputClaim ClaimTypeReferenceId="extension_activationCode" Required="true" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingEmailAddress-WithCodeDYP" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
<TechnicalProfile Id="AAD-UserReadUsingEmailAddress-WithCodeDYP">
<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" />
<InputClaim ClaimTypeReferenceId="extension_activationCode" Required="true" />
</InputClaims>
<OutputClaims>
<!-- Required claims -->
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
<!-- Optional claims -->
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="otherMails" />
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
<OutputClaim ClaimTypeReferenceId="extension_TermsOfUseConsented" />
<OutputClaim ClaimTypeReferenceId="extension_shareDataWithTP" />
<OutputClaim ClaimTypeReferenceId="extension_isAccountActivated" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
I want to hide the activation code step UI but dont want to skip this step.
Is there a way to hide from user this step?

Yes - you can do this via a magic link.
Essentially you put the email address in a signed token to tell B2C whose password to reset.
Note the new way to do this is via id_token_hint.

Related

Prevent Azure B2C users from logging in with an email address when the username login flow is enabled

I have some custom policies setup in Azure AD B2C, one of which is a flow where the user logs in with their username. I used the following github code for reference. My problem is that users that were previously setup using an email address can also login through the username flow by using their email address as the username. I want to prevent this.
Here are my first two UserJourney steps:
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
</ClaimsProviderSelections>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Username" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SignUpWithLogonUsernameExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonName" />
</ClaimsExchanges>
</OrchestrationStep>
The technical profile for SelfAsserted-LocalAccountSignin-Username.
<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Username">
<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">SignUpWithLogonUsernameExchange</Item>
<Item Key="setting.operatingMode">Username</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>
The SignUpTarget metadata is referencing SignUpWithLogonUsernameExchange which has the technical profile of LocalAccountSignUpWithLogonName as seen in step two of the UserJourney.
<TechnicalProfile Id="LocalAccountSignUpWithLogonName">
<DisplayName>Username 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="LocalAccountType">Username</Item>
<Item Key="LocalAccountProfile">true</Item>
<Item Key="language.button_continue">Create</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<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="AAD-UserWriteUsingLogonName" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
And the AAD-UserWriteUsingLogonName validation technical profile.
<TechnicalProfile Id="AAD-UserWriteUsingLogonName">
<Metadata>
<Item Key="Operation">Write</Item>
<Item Key="RaiseErrorIfClaimsPrincipalAlreadyExists">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="signInNames.userName" Required="true" />
</InputClaims>
<PersistedClaims>
<!-- Required claims -->
<PersistedClaim ClaimTypeReferenceId="signInName" PartnerClaimType="signInNames.userName" />
<PersistedClaim ClaimTypeReferenceId="email" PartnerClaimType="strongAuthenticationEmailAddress" />
<PersistedClaim ClaimTypeReferenceId="newPassword" PartnerClaimType="password" />
<PersistedClaim ClaimTypeReferenceId="displayName" DefaultValue="unknown" />
<PersistedClaim ClaimTypeReferenceId="passwordPolicies" DefaultValue="DisablePasswordExpiration" />
<!-- Optional claims. -->
<PersistedClaim ClaimTypeReferenceId="givenName" />
<PersistedClaim ClaimTypeReferenceId="surname" />
</PersistedClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="newUser" PartnerClaimType="newClaimsPrincipalCreated" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
Here are the identities for each user:
User 1
"identities":[
{
"signInType":"emailAddress",
"issuer":"{tenant}.onmicrosoft.com",
"issuerAssignedId":"userOne#yahoo.com"
},
{
"signInType":"userPrincipalName",
"issuer":"{tenant}.onmicrosoft.com",
"issuerAssignedId":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX#{tenant}.onmicrosoft.com"
}
]
User 2:
"identities":[
{
"signInType":"userName",
"issuer":"{tenant}.onmicrosoft.com",
"issuerAssignedId":"userTwo"
},
{
"signInType":"userPrincipalName",
"issuer":"{tenant}.onmicrosoft.com",
"issuerAssignedId":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX#{tenant}.onmicrosoft.com"
}
]
Any help appreciated.
In the sign in technical profile, add a validation technical profile to read the account, using signInNames.username. Configure its metadata to throw an error if the user doesn’t exist. Insert this before the login-noninteractive validation technical profile.
Solution:
<TechnicalProfile Id="AAD-ReadUsingUsername">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="signInNames.userName" Required="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
</OutputClaims>
</TechnicalProfile>
<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Username">
...snip...
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-ReadUsingUsername" />
<ValidationTechnicalProfile ReferenceId="login-NonInteractive" />
</ValidationTechnicalProfiles>
...snip...
References:
Understand the starter pack
Azure AD R/W Technical Profile

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.

B2C password reset policy using signinname and verified email

My custom pwd reset policy using signinName (not email) AND a verified email address is failing. From the portal I can see that my user was created with the correct userid and has the email in the Authentication Contact Info section. When running the reset policy I am able to verify the email with a one time code but when I press Continue I get 'Unable to validate the information provided'. I am assuming that my TP AAD-UserReadUsingSigninName, which is used as validation profile in the step collecting user id and email is failing. Why?
<UserJourney Id="PasswordResetUsingUserId">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="PasswordResetUsingUserIdAddressExchange" TechnicalProfileReferenceId="LocalAccountDiscoveryUsingUserId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="NewCredentials" TechnicalProfileReferenceId="LocalAccountWritePasswordUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
<TechnicalProfile Id="LocalAccountDiscoveryUsingUserId">
<DisplayName>Reset password using user id and 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="signinName" Required="true" />
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingSigninName" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
<TechnicalProfile Id="AAD-UserReadUsingSigninName">
<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="signinName" PartnerClaimType="signInNames.userName" Required="true" />
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="strongAuthenticationEmailAddress" Required="true" />
</InputClaims>
<OutputClaims>
<!-- Required claims -->
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
<!-- Optional claims -->
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="otherMails" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
This is occurring because an AzureActiveDirectory technical profile can only accept one input claim.
You should modify the AAD-UserReadUsingSigninName technical profile to find the user object by sign-in name and then assert that the entered email address matches the saved one:
<ClaimsTransformation Id="AssertEmailAndStrongAuthenticationEmailAddressAreEqual" TransformationMethod="AssertStringClaimsAreEqual">
<InputClaims>
<InputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" TransformationClaimType="inputClaim1" />
<InputClaim ClaimTypeReferenceId="email" TransformationClaimType="inputClaim2" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringComparison" DataType="string" Value="ordinalIgnoreCase" />
</InputParameters>
</ClaimsTransformation>
and:
<TechnicalProfile Id="AAD-UserReadUsingSigninName">
<InputClaims>
<InputClaim ClaimTypeReferenceId="signinName" PartnerClaimType="signInNames.userName" Required="true" />
</InputClaims>
<OutputClaims>
<!-- Required claims -->
<OutputClaim ClaimTypeReferenceId="objectId" />
...
<!-- Optional claims -->
...
<OutputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="AssertEmailAndStrongAuthenticationEmailAddressAreEqual" />
</OutputClaimsTransformations>
</TechnicalProfile>
You can then modify the LocalAccountDiscoveryUsingUserId technical profile to display an error message if the string comparison does fail:
<TechnicalProfile Id="LocalAccountDiscoveryUsingUserId">
<Metadata>
...
<Item Key="UserMessageIfClaimsTransformationStringsAreNotEqual">Custom error message the email addresses you provided are not the same.</Item>
</Metadata>
</TechnicalProfile>
For more information about this specific pattern, see the AssertStringClaimsAreEqual section in the String Claims Transformations article.

Resources