Custom Policy does not respect email operating mode - azure

When I run my signup_signin custom policy I receive "Sign in with your sign in name" in the UI, however I'd like for this to be an email address sign in. From what I can tell the TrustFrameworkBase has the operatingMode set to email, however I'm not certain why I'm not getting the correct outcome. Do I need to adjust the SelfAsserted-LocalAccountSignin-Email technical profile in some way?
TrustFrameworkBase unmodified from this:
https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack/blob/master/LocalAccounts/TrustFrameworkBase.xml
TrustFrameworkExtensions.xml
<?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="{Settings:Tenant}" PolicyId="B2C_1A_TrustFrameworkExtensions" PublicPolicyUri="http://{Settings:Tenant}/B2C_1A_TrustFrameworkExtensions">
<BasePolicy>
<TenantId>{Settings:Tenant}</TenantId>
<PolicyId>B2C_1A_TrustFrameworkBase</PolicyId>
</BasePolicy>
<BuildingBlocks>
<ClaimsSchema>
<!-- Custom Attributes -->
<!-- Seamless Migration -->
<ClaimType Id="extension_RequiresMigration">
<DisplayName>extension_RequiresMigration</DisplayName>
<DataType>boolean</DataType>
<AdminHelpText>Migration status for the user's account. If true, reach out to legacy IdP to migrate user.</AdminHelpText>
</ClaimType>
<ClaimType Id="TokenSuccess">
<DisplayName>TokenSuccess</DisplayName>
<DataType>boolean</DataType>
<AdminHelpText>Add help text here</AdminHelpText>
</ClaimType>
<ClaimType Id="MigrationRequired">
<DisplayName>MigrationRequired</DisplayName>
<DataType>boolean</DataType>
<AdminHelpText>Holds the value of false when the legacy IdP authentication succeeded</AdminHelpText>
</ClaimType>
</ClaimsSchema>
<ContentDefinitions>
<!-- This content definition is to render an error page that displays unhandled errors. -->
<ContentDefinition Id="api.error">
<LoadUri>~/tenant/templates/AzureBlue/exception.cshtml</LoadUri>
<RecoveryUri>~/common/default_page_error.html</RecoveryUri>
<DataUri>urn:com:microsoft:aad:b2c:elements:contract:globalexception:1.2.0</DataUri>
<Metadata>
<Item Key="DisplayName">Error page</Item>
</Metadata>
</ContentDefinition>
<ContentDefinition Id="api.idpselections">
<LoadUri>~/tenant/templates/AzureBlue/idpSelector.cshtml</LoadUri>
<RecoveryUri>~/common/default_page_error.html</RecoveryUri>
<DataUri>urn:com:microsoft:aad:b2c:elements:contract:providerselection:1.2.0</DataUri>
<Metadata>
<Item Key="DisplayName">Idp selection page</Item>
<Item Key="language.intro">Sign in</Item>
</Metadata>
</ContentDefinition>
<ContentDefinition Id="api.idpselections.signup">
<LoadUri>~/tenant/templates/AzureBlue/idpSelector.cshtml</LoadUri>
<RecoveryUri>~/common/default_page_error.html</RecoveryUri>
<DataUri>urn:com:microsoft:aad:b2c:elements:contract:providerselection:1.2.0</DataUri>
<Metadata>
<Item Key="DisplayName">Idp selection page</Item>
<Item Key="language.intro">Sign up</Item>
</Metadata>
</ContentDefinition>
<ContentDefinition Id="api.signuporsignin">
<LoadUri>https://{Settings:BlobStorageAccount}.blob.core.windows.net/{Settings:BlobContainer}/html/sign_in.html</LoadUri>
<RecoveryUri>~/common/default_page_error.html</RecoveryUri>
<DataUri>urn:com:microsoft:aad:b2c:elements:contract:unifiedssp:2.1.0</DataUri>
<Metadata>
<Item Key="DisplayName">Signin and Signup</Item>
</Metadata>
</ContentDefinition>
<ContentDefinition Id="api.selfasserted">
<LoadUri>~/tenant/templates/AzureBlue/selfAsserted.cshtml</LoadUri>
<RecoveryUri>~/common/default_page_error.html</RecoveryUri>
<DataUri>urn:com:microsoft:aad:b2c:elements:contract:selfasserted:2.1.0</DataUri>
<Metadata>
<Item Key="DisplayName">Collect information from user page</Item>
</Metadata>
</ContentDefinition>
<ContentDefinition Id="api.selfasserted.profileupdate">
<LoadUri>~/tenant/templates/AzureBlue/selfAsserted.cshtml</LoadUri>
<RecoveryUri>~/common/default_page_error.html</RecoveryUri>
<DataUri>urn:com:microsoft:aad:b2c:elements:contract:selfasserted:2.1.0</DataUri>
<Metadata>
<Item Key="DisplayName">Collect information from user page</Item>
</Metadata>
</ContentDefinition>
<ContentDefinition Id="api.localaccountsignup">
<LoadUri>~/tenant/templates/AzureBlue/selfAsserted.cshtml</LoadUri>
<RecoveryUri>~/common/default_page_error.html</RecoveryUri>
<DataUri>urn:com:microsoft:aad:b2c:elements:contract:selfasserted:2.1.0</DataUri>
<Metadata>
<Item Key="DisplayName">Local account sign up page</Item>
</Metadata>
</ContentDefinition>
<ContentDefinition Id="api.localaccountpasswordreset">
<LoadUri>~/tenant/templates/AzureBlue/selfAsserted.cshtml</LoadUri>
<RecoveryUri>~/common/default_page_error.html</RecoveryUri>
<DataUri>urn:com:microsoft:aad:b2c:elements:contract:selfasserted:2.1.0</DataUri>
<Metadata>
<Item Key="DisplayName">Local account change password page</Item>
</Metadata>
</ContentDefinition>
</ContentDefinitions>
</BuildingBlocks>
<ClaimsProviders>
<!-- Allows for referencing custom attributes in custom policies -->
<!-- https://learn.microsoft.com/en-us/azure/active-directory-b2c/custom-policy-custom-attributes#modify-your-custom-policy -->
<ClaimsProvider>
<DisplayName>Azure Active Directory</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="AAD-Common">
<Metadata>
<Item Key="ClientId">{Settings:B2CExtensionsAppId}</Item>
<Item Key="ApplicationObjectId">{Settings:B2CExtensionsObjectId}</Item>
</Metadata>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<!-- Seamless Migration -->
<ClaimsProvider>
<DisplayName>Local Account SignIn - Read Migration Status</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="Get-RequiresMigration-Status-SignIn">
<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.emailAddress" Required="true"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId"/>
<OutputClaim ClaimTypeReferenceId="extension_RequiresMigration" DefaultValue="false"/>
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common"/>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>REST API to communicate with Legacy IdP</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="UserMigrationViaLegacyIdP">
<DisplayName>REST API call to communicate with Legacy IdP</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://{Settings:TIAPIBaseUrl}/SSOAuth</Item>
<Item Key="AuthenticationType">None</Item>
<Item Key="SendClaimsIn">Body</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="email"/>
<InputClaim ClaimTypeReferenceId="password"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="TokenSuccess" DefaultValue="false"/>
<OutputClaim ClaimTypeReferenceId="MigrationRequired"/>
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop"/>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Local Account SignIn - Write new password and unmark for migration</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="AAD-WritePasswordAndFlipMigratedFlag">
<Metadata>
<Item Key="Operation">Write</Item>
<Item Key="RaiseErrorIfClaimsPrincipalAlreadyExists">false</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true"/>
</InputClaims>
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="objectId"/>
<PersistedClaim ClaimTypeReferenceId="userPrincipalName" />
<PersistedClaim ClaimTypeReferenceId="displayName" />
<PersistedClaim ClaimTypeReferenceId="password" PartnerClaimType="password"/>
<PersistedClaim ClaimTypeReferenceId="passwordPolicies" DefaultValue="DisablePasswordExpiration, DisableStrongPassword" AlwaysUseDefaultValue="true"/>
<PersistedClaim ClaimTypeReferenceId="MigrationRequired" PartnerClaimType="extension_RequiresMigration"/>
</PersistedClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common"/>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD"/>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<!-- End Seamless Migration -->
<ClaimsProvider>
<DisplayName>Local Account SignIn</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="login-NonInteractive">
<Metadata>
<Item Key="client_id">{Settings:ProxyIdentityExperienceFrameworkAppId}</Item>
<Item Key="IdTokenAudience">{Settings:IdentityExperienceFrameworkAppId}</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="client_id" DefaultValue="{Settings:ProxyIdentityExperienceFrameworkAppId}" />
<InputClaim ClaimTypeReferenceId="resource_id" PartnerClaimType="resource" DefaultValue="{Settings:IdentityExperienceFrameworkAppId}" />
</InputClaims>
</TechnicalProfile>
<!-- Seamless Migration -->
<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Email">
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="extension_RequiresMigration"/>
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="Get-RequiresMigration-Status-SignIn" ContinueOnError="false"/>
<ValidationTechnicalProfile ReferenceId="UserMigrationViaLegacyIdP" ContinueOnError="false">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>extension_RequiresMigration</Value>
<Value>False</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
<ValidationTechnicalProfile ReferenceId="AAD-WritePasswordAndFlipMigratedFlag" ContinueOnError="false">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>TokenSuccess</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
<ValidationTechnicalProfile ReferenceId="login-NonInteractive"/>
</ValidationTechnicalProfiles>
</TechnicalProfile>
<!-- End Seamless Migration-->
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<!--UserJourneys>
</UserJourneys-->
</TrustFrameworkPolicy>
SignUpOrSignin.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<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="{Settings:Tenant}" PolicyId="B2C_1A_SignUp_Signin" PublicPolicyUri="http://{Settings:Tenant}/B2C_1A_SignUp_Signin" UserJourneyRecorderEndpoint="urn:journeyrecorder:applicationinsights" DeploymentMode="{Settings:ApplicationInsightsDeploymentMode}">
<BasePolicy>
<TenantId>{Settings:Tenant}</TenantId>
<PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId>
</BasePolicy>
<RelyingParty>
<DefaultUserJourney ReferenceId="SignUpOrSignIn" />
<UserJourneyBehaviors>
<JourneyInsights TelemetryEngine="ApplicationInsights" InstrumentationKey="{Settings:ApplicationInsightsInstrumentationKey}" DeveloperMode="{Settings:ApplicationInsightsDeveloperMode}" ClientEnabled="false" ServerEnabled="true" TelemetryVersion="1.0.0" />
<ScriptExecution>Allow</ScriptExecution>
</UserJourneyBehaviors>
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<InputClaims>
<InputClaim ClaimTypeReferenceId="passwordPolicies" DefaultValue="DisablePasswordExpiration, DisableStrongPassword" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surname" />
<OutputClaim ClaimTypeReferenceId="email" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub"/>
<OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
</TrustFrameworkPolicy>

To fix email field type, use <UserInputType>EmailBox</UserInputType>.
<ClaimsSchema>
<ClaimType Id="signInName">
<DisplayName>Email Address</DisplayName>
<DataType>string</DataType>
<AdminHelpText>Email address that the user can use to sign in.</AdminHelpText>
<UserHelpText>Email address to use for signing in.</UserHelpText>
<UserInputType>EmailBox</UserInputType>
</ClaimType>
</ClaimsSchema>
We will try make this default in the starter pack.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/claimsschema#userinputtype

This seems to be an issue with Page Layout Version 2.1.x as indicated by JasSuri. My current workaround working solution:
<LocalizedString ElementType="UxElement"
StringId="local_intro_generic">Sign in with your email address</LocalizedString>
<LocalizedString ElementType="ClaimType"
ElementId="signInName"
StringId="DisplayName">Email Address</LocalizedString>
Its not perfect, but fine for the moment for me. The main problem is that the field is no longer of type email, which causes some issues (e.g. mobile keyboards, password managers)
edit: Adding the code by JasSuri in order to make this one "complete" answer:
To fix email field type, use <UserInputType>EmailBox</UserInputType>.
<ClaimsSchema>
<ClaimType Id="signInName">
<DisplayName>Email Address</DisplayName>
<DataType>string</DataType>
<AdminHelpText>Email address that the user can use to sign in.</AdminHelpText>
<UserHelpText>Email address to use for signing in.</UserHelpText>
<UserInputType>EmailBox</UserInputType>
</ClaimType>
</ClaimsSchema>

Related

Validation error uploading Azure AD B2C policy

I created an Azure AD B2C tenant with custom policies last year. Now I am trying to upload the same policies (with IDs changed as necessary) to a new tenant that we have just created and I get the following error when uploading the reset-password policy:
Validation failed: 1 validation error(s) found in policy "B2C_1A_PASSWORDRESET" of tenant "xxx.onmicrosoft.com".Persisted claims for technical profile "AAD-FlipMigratedFlag" in policy "B2C_1A_PasswordReset" of tenant "xxx.onmicrosoft.com" must have one of the following claims: userPrincipalName
These policies implement the Seamless Migration approach to user migration, based on samples in the following repositories:
https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack
https://github.com/azure-ad-b2c/samples
https://github.com/azure-ad-b2c/user-migration
As suggested by the error message, I have tried adding userPrincipalName to the PersistedClaims for the AAD-FlipMigratedFlag technical profile, but I get the same error when uploading the policy.
I have also tried re-uploading the existing, working reset-password policy to the existing, working tenant, and I get the same error. Note that in this case I am re-uploading the exact same policy that has already been successfully uploaded and has been working for a year.
So the question is: what has changed and what do I need to do to fix this error?
Here are the relevant parts of my custom policy files. If there are any other parts you need to see, just let me know and I'll add them.
PasswordReset.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<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="xxx.onmicrosoft.com"
PolicyId="B2C_1A_PasswordReset"
PublicPolicyUri="http://xxx.onmicrosoft.com/B2C_1A_PasswordReset">
<BasePolicy>
<TenantId>xxx.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId>
</BasePolicy>
<RelyingParty>
<DefaultUserJourney ReferenceId="PasswordReset" />
<UserJourneyBehaviors>
<ScriptExecution>Allow</ScriptExecution>
</UserJourneyBehaviors>
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub"/>
<OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="emails" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
</TrustFrameworkPolicy>
TrustFrameworkExtensions.xml
<?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="xxx.onmicrosoft.com"
PolicyId="B2C_1A_TrustFrameworkExtensions"
PublicPolicyUri="http://xxx.onmicrosoft.com/B2C_1A_TrustFrameworkExtensions">
<BasePolicy>
<TenantId>xxx.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkBase</PolicyId>
</BasePolicy>
<BuildingBlocks>
<ClaimsSchema>
<!-- Holds the value of the migration status on the Azure AD B2C account -->
<ClaimType Id="extension_IsMigrationRequired">
<DisplayName>extension_IsMigrationRequired</DisplayName>
<DataType>boolean</DataType>
<AdminHelpText>extension_IsMigrationRequired</AdminHelpText>
<UserHelpText>extension_IsMigrationRequired</UserHelpText>
</ClaimType>
<!-- Holds the value of whether the authentication succeeded at the legacy IdP -->
<ClaimType Id="tokenSuccess">
<DisplayName>tokenSuccess</DisplayName>
<DataType>boolean</DataType>
<AdminHelpText>tokenSuccess</AdminHelpText>
<UserHelpText>tokenSuccess</UserHelpText>
</ClaimType>
<!-- Holds the value 'false' when the legacy IdP authentication succeeded -->
<ClaimType Id="migrationRequired">
<DisplayName>migrationRequired</DisplayName>
<DataType>boolean</DataType>
<AdminHelpText>migrationRequired</AdminHelpText>
<UserHelpText>migrationRequired</UserHelpText>
</ClaimType>
</ClaimsSchema>
</BuildingBlocks>
<ClaimsProviders>
<ClaimsProvider>
<DisplayName>Local Account Password Reset - Write Password</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="LocalAccountWritePasswordUsingObjectId">
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="Get-requiresMigration-status-password-reset" ContinueOnError="false" />
<ValidationTechnicalProfile ReferenceId="AAD-FlipMigratedFlag" ContinueOnError="false">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>extension_IsMigrationRequired</Value>
<Value>False</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
<ValidationTechnicalProfile ReferenceId="AAD-UserWritePasswordUsingObjectId" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Local Account Password Reset - Read migration flag</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="Get-requiresMigration-status-password-reset">
<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="objectId" Required="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="extension_IsMigrationRequired" DefaultValue="false" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Local Account Password Reset - Flip migration flag</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="AAD-FlipMigratedFlag">
<Metadata>
<Item Key="Operation">Write</Item>
<Item Key="RaiseErrorIfClaimsPrincipalAlreadyExists">false</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true" />
</InputClaims>
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="objectId" />
<PersistedClaim ClaimTypeReferenceId="migrationRequired" PartnerClaimType="extension_IsMigrationRequired" DefaultValue="false" AlwaysUseDefaultValue="true"/>
<!-- NOTE: I added this but still get the error -->
<PersistedClaim ClaimTypeReferenceId="userPrincipalName" />
</PersistedClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Azure Active Directory</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="AAD-Common">
<Metadata>
<!--Insert b2c-extensions-app application ID here, for example: 11111111-1111-1111-1111-111111111111-->
<Item Key="ClientId">xxx</Item>
<!--Insert b2c-extensions-app application ObjectId here, for example: 22222222-2222-2222-2222-222222222222-->
<Item Key="ApplicationObjectId">xxx</Item>
</Metadata>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
</TrustFrameworkPolicy>
TrustFrameworkBase.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<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="xxx.onmicrosoft.com"
PolicyId="B2C_1A_TrustFrameworkBase"
PublicPolicyUri="http://xxx.onmicrosoft.com/B2C_1A_TrustFrameworkBase">
<ClaimsProviders>
<ClaimsProvider>
<TechnicalProfiles>
<TechnicalProfile Id="LocalAccountDiscoveryUsingEmailAddress">
<DisplayName>Reset password using email address</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="IpAddressClaimReferenceId">IpAddress</Item>
<Item Key="ContentDefinitionReferenceId">api.localaccountpasswordreset</Item>
<Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Your account has been locked. Contact your support person to unlock it, then try again.</Item>
<Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<IncludeInSso>false</IncludeInSso>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingEmailAddress" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
<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>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
<TechnicalProfile Id="AAD-Common">
<DisplayName>Azure Active Directory</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.AzureActiveDirectoryProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<!-- We need this here to suppress the SelfAsserted provider from invoking SSO on validation profiles. -->
<IncludeInSso>false</IncludeInSso>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<UserJourneys>
<UserJourney Id="PasswordReset">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="PasswordResetUsingEmailAddressExchange" TechnicalProfileReferenceId="LocalAccountDiscoveryUsingEmailAddress" />
</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>
</UserJourneys>
</TrustFrameworkPolicy>
I was hoping someone from MS would chime in on this. Since that hasn't happened, I'll go ahead and post my solution.
MS has a bad habit of creating schema where the order of the nodes matter. That was part of the problem here. In addition, the displayName claim was also added to the required list.
So after some trial-and-error, this version of the AAD-FlipMigratedFlag claims provider ended up being the solution:
<ClaimsProvider>
<DisplayName>Local Account Password Reset - Flip migration flag</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="AAD-FlipMigratedFlag">
<Metadata>
<Item Key="Operation">Write</Item>
<Item Key="RaiseErrorIfClaimsPrincipalAlreadyExists">false</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true" />
</InputClaims>
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="objectId" />
<PersistedClaim ClaimTypeReferenceId="displayName" />
<PersistedClaim ClaimTypeReferenceId="userPrincipalName" />
<PersistedClaim ClaimTypeReferenceId="migrationRequired" PartnerClaimType="extension_IsMigrationRequired" DefaultValue="false" AlwaysUseDefaultValue="true"/>
</PersistedClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
Have yout tried to add the userPrincipalName claim to the OutputClaims section of PasswordReset.xml?

AzureAD B2C Sign In Custom Policy returns "Invalid username or password."

Using the SignInAndSignUp custom policy, I can sign up and reset password successfully, I am logged in after Sign Up, but for some reason I can't Sign In.
I have the ApplicationIds set in TrustFrameworkExtensions.xml
Here is some data I got from AzureAD B2C VS Code Application Insights Extension:
Exceptions: Invalid username or password.
Validation technical profiles: login-NonInteractive
{
"Key": "Exception",
"Value": {
"Kind": "Handled",
"HResult": "80131500",
"Message": "Invalid username or password.",
"Data": {
"IsPolicySpecificError": false
}
}
}
login-NonInteractive in TrustFrameworkBase.xml:
<ClaimsProvider>
<DisplayName>Local Account SignIn</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="login-NonInteractive">
<DisplayName>Local Account SignIn</DisplayName>
<Protocol Name="OpenIdConnect" />
<Metadata>
<Item Key="ProviderName">https://sts.windows.net/</Item>
<Item Key="METADATA">https://login.microsoftonline.com/{tenant}/.well-known/openid-configuration</Item>
<Item Key="authorization_endpoint">https://login.microsoftonline.com/{tenant}/oauth2/token</Item>
<Item Key="response_types">id_token</Item>
<Item Key="response_mode">query</Item>
<Item Key="scope">email openid</Item>
<!-- <Item Key="grant_type">password</Item> -->
<!-- Policy Engine Clients -->
<Item Key="UsePolicyInRedirectUri">false</Item>
<Item Key="HttpBinding">POST</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="username" Required="true" />
<InputClaim ClaimTypeReferenceId="password" Required="true" />
<InputClaim ClaimTypeReferenceId="grant_type" DefaultValue="password" />
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="openid" />
<InputClaim ClaimTypeReferenceId="nca" PartnerClaimType="nca" DefaultValue="1" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="oid" />
<OutputClaim ClaimTypeReferenceId="tenantId" PartnerClaimType="tid" />
<OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="name" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" PartnerClaimType="upn" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
</OutputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
login-NonInteractive in TrustFrameworkExtensions.xml:
<ClaimsProvider>
<DisplayName>Local Account SignIn</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="login-NonInteractive">
<Metadata>
<Item Key="client_id">I have it set, but removed for question</Item>
<Item Key="IdTokenAudience">I have it set, but removed for question</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="client_id" DefaultValue="I have it set, but removed for question" />
<InputClaim ClaimTypeReferenceId="resource_id" PartnerClaimType="resource" DefaultValue="I have it set, but removed for question" />
</InputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
Any help?
This is always because you did not follow this process accurately.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-custom-policy#register-identity-experience-framework-applications
And
https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-custom-policy#add-application-ids-to-the-custom-policy
You can use my setup tool instead to automate the process. Just delete the two application registrations (proxyief and ief) first. https://aka.ms/iefsetup.

Azure B2C Custom UI empty div API

I am trying to customize the password reset page in Azure B2C. I followed the documentation of Microsoft: https://learn.microsoft.com/en-us/azure/active-directory-b2c/customize-ui-with-html?pivots=b2c-custom-policy
everything looks ok, but after opening the page with run now in custom policy the custom page is showing without the fields from the API.
empty api
for test I set this custom page in the userflow, and over there the page is showing with the input fields. So it looks like there is something wrong in the custom policy, but everything looks ok I guess.
<ContentDefinition Id="api.localaccountpasswordreset">
<LoadUri>https://company.blob.core.windows.net/azure-b2c/custom-selfAsserted.html</LoadUri>
<RecoveryUri>~/common/default_page_error.html</RecoveryUri>
<DataUri>urn:com:microsoft:aad:b2c:elements:selfasserted:1.1.0</DataUri>
<Metadata>
<Item Key="DisplayName">Local account change password page</Item>
</Metadata>
</ContentDefinition>
Does someone have a suggestion why the API fields aren't showing up?
Custom policy of Sendgrid extensions:
<?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_SendGrid_Extensions"
PublicPolicyUri="http://tenant.onmicrosoft.com/B2C_1A_TrustFrameworkExtensions">
<BasePolicy>
<TenantId>tenant.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkBase</PolicyId>
</BasePolicy>
<BuildingBlocks>
<ClaimsSchema>
<ClaimType Id="Otp">
<DisplayName>Secondary One-time password</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="emailRequestBody">
<DisplayName>SendGrid request body</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="VerificationCode">
<DisplayName>Secondary Verification Code</DisplayName>
<DataType>string</DataType>
<UserHelpText>Enter your email verification code</UserHelpText>
<UserInputType>TextBox</UserInputType>
</ClaimType>
</ClaimsSchema>
<ClaimsTransformations>
<ClaimsTransformation Id="GenerateEmailRequestBody" TransformationMethod="GenerateJson">
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" TransformationClaimType="personalizations.0.to.0.email" />
<InputClaim ClaimTypeReferenceId="otp" TransformationClaimType="personalizations.0.dynamic_template_data.otp" />
<InputClaim ClaimTypeReferenceId="email" TransformationClaimType="personalizations.0.dynamic_template_data.email" />
</InputClaims>
<InputParameters>
<!-- Update the template_id value with the ID of your SendGrid template. -->
<InputParameter Id="template_id" DataType="string" Value="d-8d30a9a4de9a4a82cd6b1"/>
<InputParameter Id="from.email" DataType="string" Value="email#domain.com"/>
<!-- Update with a subject line appropriate for your organization. -->
<InputParameter Id="personalizations.0.dynamic_template_data.subject" DataType="string" Value="Email verification"/>
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="emailRequestBody" TransformationClaimType="outputClaim"/>
</OutputClaims>
</ClaimsTransformation>
</ClaimsTransformations>
<ContentDefinitions>
<ContentDefinition Id="api.localaccountsignup">
<DataUri>urn:com:microsoft:aad:b2c:elements:contract:selfasserted:2.1.0</DataUri>
</ContentDefinition>
<ContentDefinition Id="api.localaccountpasswordreset">
<LoadUri>https://b2cstorage.blob.core.windows.net/azure-b2c/custom-selfAsserted.html</LoadUri>
<RecoveryUri>~/common/default_page_error.html</RecoveryUri>
<DataUri>urn:com:microsoft:aad:b2c:elements:contract:selfasserted:2.1.0</DataUri>
<Metadata>
<Item Key="DisplayName">Local account change password page</Item>
</Metadata>
</ContentDefinition>
</ContentDefinitions>
<DisplayControls>
<DisplayControl Id="emailVerificationControl" UserInterfaceControlType="VerificationControl">
<DisplayClaims>
<DisplayClaim ClaimTypeReferenceId="email" Required="true" />
<DisplayClaim ClaimTypeReferenceId="verificationCode" ControlClaimType="VerificationCode" Required="true" />
</DisplayClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" />
</OutputClaims>
<Actions>
<Action Id="SendCode">
<ValidationClaimsExchange>
<ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="GenerateOtp" />
<ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="SendOtp" />
</ValidationClaimsExchange>
</Action>
<Action Id="VerifyCode">
<ValidationClaimsExchange>
<ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="VerifyOtp" />
</ValidationClaimsExchange>
</Action>
</Actions>
</DisplayControl>
</DisplayControls>
</BuildingBlocks>
<ClaimsProviders>
<ClaimsProvider>
<DisplayName>Local Account SignIn</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="login-NonInteractive">
<Metadata>
<Item Key="client_id">ProxyIdentityExperienceFrameworkAppId</Item>
<Item Key="IdTokenAudience">IdentityExperienceFrameworkAppId</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="client_id" DefaultValue="ProxyIdentityExperienceFrameworkAppId" />
<InputClaim ClaimTypeReferenceId="resource_id" PartnerClaimType="resource" DefaultValue="IdentityExperienceFrameworkAppId" />
</InputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>One time password technical profiles</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="GenerateOtp">
<DisplayName>Generate one time password</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.OneTimePasswordProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="Operation">GenerateCode</Item>
<Item Key="CodeExpirationInSeconds">1200</Item>
<Item Key="CodeLength">6</Item>
<Item Key="CharacterSet">0-9</Item>
<Item Key="ReuseSameCode">true</Item>
<Item Key="MaxNumAttempts">5</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="identifier" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="otp" PartnerClaimType="otpGenerated" />
</OutputClaims>
</TechnicalProfile>
<TechnicalProfile Id="VerifyOtp">
<DisplayName>Verify one time password</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.OneTimePasswordProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="Operation">VerifyCode</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="identifier" />
<InputClaim ClaimTypeReferenceId="verificationCode" PartnerClaimType="otpToVerify" />
</InputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>RestfulProvider</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="SendOtp">
<DisplayName>Use SendGrid's email API to send the code the the user</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://api.sendgrid.com/v3/mail/send</Item>
<Item Key="AuthenticationType">Bearer</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="ClaimUsedForRequestPayload">emailRequestBody</Item>
</Metadata>
<CryptographicKeys>
<Key Id="BearerAuthenticationToken" StorageReferenceId="B2C_1A_SendGridSecret" />
</CryptographicKeys>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="GenerateEmailRequestBody" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="emailRequestBody" />
</InputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Local Account</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="LocalAccountSignUpWithLogonEmail">
<Metadata>
<!--OTP validation error messages-->
<Item Key="UserMessageIfSessionDoesNotExist">You have exceeded the maximum time allowed.</Item>
<Item Key="UserMessageIfMaxRetryAttempted">You have exceeded 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>
<DisplayClaims>
<DisplayClaim DisplayControlReferenceId="emailVerificationControl" />
<DisplayClaim ClaimTypeReferenceId="displayName" Required="true" />
<DisplayClaim ClaimTypeReferenceId="givenName" Required="true" />
<DisplayClaim ClaimTypeReferenceId="surName" Required="true" />
<DisplayClaim ClaimTypeReferenceId="newPassword" Required="true" />
<DisplayClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
</DisplayClaims>
</TechnicalProfile>
<TechnicalProfile Id="LocalAccountDiscoveryUsingEmailAddress">
<Metadata>
<!--OTP validation error messages-->
<Item Key="UserMessageIfSessionDoesNotExist">You have exceeded the maximum time allowed.</Item>
<Item Key="UserMessageIfMaxRetryAttempted">You have exceeded 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>
<DisplayClaims>
<DisplayClaim DisplayControlReferenceId="emailVerificationControl" />
</DisplayClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<!--UserJourneys>
</UserJourneys-->
</TrustFrameworkPolicy>
the is urn:com:microsoft:aad:b2c:elements:contract:selfasserted:2.1.0
which may conflict with the template. I also played around with this section, but no success.

ADB2C Custom policy for sending email through sendgrid

Other than optional steps, I have followed all steps mentioned below:
https://learn.microsoft.com/en-us/azure/active-directory-b2c/custom-email-sendgrid
I keep getting the following error when clicking through the "Send Veirification code" button -
Basic credentials specified for 'SendOtp' are invalid. Check that the credentials are correct and that access has been granted by the resource.
The sendgrid account is configured correctly and I am able to send email through Postman. The postman request, using the same sendgrid api key I am setting in Azure policy key
{
"personalizations": [
{
"to": [
{
"email": "abc#abc.com",
"name": "abc abc"
}
],
"dynamic_template_data": {
"otp": "123456",
"subject": "account email verification code",
},
}
],
"from": {
"email": "noreply#johndoe.com",
"name": "John Doe"
},
"reply_to": {
"email": "noreply#johndoe.com",
"name": "John Doe"
},
"template_id": "d-xxxxxxxxxxxxxxxxxxxxxxxx"
}
These are the blocks from the above link that I have added
<!--Step 1 SendGrid Email-->
<ClaimType Id="Otp">
<DisplayName>Secondary One-time password</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="emailRequestBody">
<DisplayName>SendGrid request body</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="VerificationCode">
<DisplayName>Secondary Verification Code</DisplayName>
<DataType>string</DataType>
<UserHelpText>Enter your email verification code</UserHelpText>
<UserInputType>TextBox</UserInputType>
</ClaimType>
<!--Step 1-->
<!--Step 2 SendGrid Email To be worked on -->
<ClaimsTransformation Id="GenerateEmailRequestBody" TransformationMethod="GenerateJson">
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" TransformationClaimType="personalizations.0.to.0.email" />
<InputClaim ClaimTypeReferenceId="otp" TransformationClaimType="personalizations.0.dynamic_template_data.otp" />
<InputClaim ClaimTypeReferenceId="email" TransformationClaimType="personalizations.0.dynamic_template_data.email" />
</InputClaims>
<InputParameters>
<!-- Update the template_id value with the ID of your SendGrid template. -->
<InputParameter Id="template_id" DataType="string" Value="d-xxxxxxxxxxxxxxxxxxxxxxxxxx"/>
<InputParameter Id="from.email" DataType="string" Value="my_email#mydomain.com"/>
<!-- Update with a subject line appropriate for your organization. -->
<InputParameter Id="personalizations.0.dynamic_template_data.subject" DataType="string" Value="account email verification code"/>
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="emailRequestBody" TransformationClaimType="outputClaim"/>
</OutputClaims>
</ClaimsTransformation>
<!--Step 2-->
<!--Step 3 SendGrid Email-->
<ContentDefinition Id="api.localaccountsignup">
<DataUri>urn:com:microsoft:aad:b2c:elements:contract:selfasserted:2.1.0</DataUri>
</ContentDefinition>
<ContentDefinition Id="api.localaccountpasswordreset">
<DataUri>urn:com:microsoft:aad:b2c:elements:contract:selfasserted:2.1.0</DataUri>
</ContentDefinition>
<!--Step 3-->
<!--Step 4 SendGrid Email-->
<DisplayControls>
<DisplayControl Id="emailVerificationControl" UserInterfaceControlType="VerificationControl">
<DisplayClaims>
<DisplayClaim ClaimTypeReferenceId="email" Required="true" />
<DisplayClaim ClaimTypeReferenceId="verificationCode" ControlClaimType="VerificationCode" Required="true" />
</DisplayClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" />
</OutputClaims>
<Actions>
<Action Id="SendCode">
<ValidationClaimsExchange>
<ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="GenerateOtp" />
<ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="SendOtp" />
</ValidationClaimsExchange>
</Action>
<Action Id="VerifyCode">
<ValidationClaimsExchange>
<ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="VerifyOtp" />
</ValidationClaimsExchange>
</Action>
</Actions>
</DisplayControl>
</DisplayControls>
<!--Step 4-->
<!--Step 5 SendGrid Email-->
<ClaimsProvider>
<DisplayName>One time password technical profiles</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="GenerateOtp">
<DisplayName>Generate one time password</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.OneTimePasswordProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="Operation">GenerateCode</Item>
<Item Key="CodeExpirationInSeconds">1200</Item>
<Item Key="CodeLength">6</Item>
<Item Key="CharacterSet">0-9</Item>
<Item Key="ReuseSameCode">true</Item>
<Item Key="NumRetryAttempts">5</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="identifier" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="otp" PartnerClaimType="otpGenerated" />
</OutputClaims>
</TechnicalProfile>
<TechnicalProfile Id="VerifyOtp">
<DisplayName>Verify one time password</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.OneTimePasswordProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="Operation">VerifyCode</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="identifier" />
<InputClaim ClaimTypeReferenceId="verificationCode" PartnerClaimType="otpToVerify" />
</InputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<!--Step 5-->
<!--Step 6 SendGrid Email-->
<ClaimsProvider>
<DisplayName>RestfulProvider</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="SendOtp">
<DisplayName>Use SendGrid's email API to send the code the the user</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://api.sendgrid.com/v3/mail/send</Item>
<Item Key="AuthenticationType">Bearer</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="ClaimUsedForRequestPayload">emailRequestBody</Item>
</Metadata>
<CryptographicKeys>
<Key Id="BearerAuthenticationToken" StorageReferenceId="B2C_1A_SendGridSecret" />
</CryptographicKeys>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="GenerateEmailRequestBody" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="emailRequestBody" />
</InputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<!--Step 6-->
<!--Step 7 SendGrid Email-->
<ClaimsProvider>
<DisplayName>Local Account</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="LocalAccountSignUpWithLogonEmail">
<Metadata>
<!--OTP validation error messages-->
<Item Key="UserMessageIfSessionDoesNotExist">You have exceeded the maximum time allowed.</Item>
<Item Key="UserMessageIfMaxRetryAttempted">You have exceeded 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>
<DisplayClaims>
<DisplayClaim DisplayControlReferenceId="emailVerificationControl" />
<DisplayClaim ClaimTypeReferenceId="displayName" Required="true" />
<DisplayClaim ClaimTypeReferenceId="givenName" Required="true" />
<DisplayClaim ClaimTypeReferenceId="surName" Required="true" />
<DisplayClaim ClaimTypeReferenceId="newPassword" Required="true" />
<DisplayClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
</DisplayClaims>
</TechnicalProfile>
<TechnicalProfile Id="LocalAccountDiscoveryUsingEmailAddress">
<Metadata>
<!--OTP validation error messages-->
<Item Key="UserMessageIfSessionDoesNotExist">You have exceeded the maximum time allowed.</Item>
<Item Key="UserMessageIfMaxRetryAttempted">You have exceeded 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>
<DisplayClaims>
<DisplayClaim DisplayControlReferenceId="emailVerificationControl" />
</DisplayClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<!--Step 7-->
Can any one help me figure how to debug this problem. Many thanks.
Try to replace "my_email#mydomain.com" in the block with SendGrid account>Single Sender Verification> verified "DOMAIN"
Like here
I encountered the same issue. To fix it you have to do the following steps:
Add a Sender: To do so, navigate Sender Authentication page, and click Create New Sender. Next, you have to fill in all of the fields on the page and then click Save.
Verify the sender: You will receive a verification email that you need to confirm.
Update the Custom policy's from.email parameter
<ClaimsTransformations>
<ClaimsTransformation Id="GenerateEmailRequestBody" TransformationMethod="GenerateJson">
...
<InputParameter Id="from.email" DataType="string" Value="XXX#gmail.com"/>
...
</ClaimsTransformation>
</ClaimsTransformations>
Update the Custom policy's template_id parameter: If you are using a dynamic template you must update the template ID.
<ClaimsTransformations>
<ClaimsTransformation Id="GenerateEmailRequestBody" TransformationMethod="GenerateJson">
...
<InputParameter Id="template_id" DataType="string" Value="d-ac01c68f69364014adc44c7857f95d2e"/>
...
</ClaimsTransformation>
</ClaimsTransformations>

Password Reset link Azure B2C Custom Policy

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

Resources