Reputation: 111
I have a requirement of customizing the email sent to the user, from AD B2C, when she/he resets the password.
I followed this documentation to set up the self-service password reset flow, and it works fine: https://learn.microsoft.com/en-us/azure/active-directory-b2c/add-password-reset-policy?pivots=b2c-custom-policy
To provide a branded email for the password reset, I'm following this code, since it looks like that the only other option is to use Display Controls, which are currently in public preview (so I cannot use them in production): https://github.com/azure-ad-b2c/samples/tree/master/policies/custom-email-verifcation
The readme clearly states that it can be used also for the password reset, but the code only provides an example for the sign in email verification.
I tried to add the verificationCode
OutputClaim
in various TechnicalProfiles
, but I'm unable to visualize the custom verificationCode
textbox that is needed by the provided javascript code.
I'm thinking that maybe I should use a specific ContentDefinition, but I'm really struggling to find the correct way to update the custom policy xml.
Update to clarify: In the sign up example, the verification code is added to the LocalAccountSignUpWithLogonEmail
TechnicalProfile
:
<ClaimsProvider>
<DisplayName>Local Account</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="LocalAccountSignUpWithLogonEmail">
<DisplayName>Email signup</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<Metadata>
<!-- Demo: Disable the email verification-->
<Item Key="EnforceEmailVerification">False</Item>
</Metadata>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId"/>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true"/>
<!--Demo: Add the verification code claim type-->
<OutputClaim ClaimTypeReferenceId="verificationCode" Required="true"/>
Since I'm working on the password reset (orchestrated by the following SubJourney
), we can see that it references the LocalAccountDiscoveryUsingEmailAddress
TechnicalProfile
in the first step:
<SubJourney Id="PasswordReset" Type="Call">
<OrchestrationSteps>
<!--Sample: Validate user's email address. Run this step only when user resets the password-->
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="PasswordResetUsingEmailAddressExchange" TechnicalProfileReferenceId="LocalAccountDiscoveryUsingEmailAddress" />
</ClaimsExchanges>
</OrchestrationStep>
<!--Sample: Collect and persist a new password. Run this step only when user resets the password-->
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="NewCredentials" TechnicalProfileReferenceId="LocalAccountWritePasswordUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
</OrchestrationSteps>
</SubJourney>
Therefore, I added verificationCode
to the LocalAccountDiscoveryUsingEmailAddress
TechnicalProfile
:
<!-- This technical profile forces the user to verify the email address that they provide on the UI. Only after email is verified, the user account is
read from the directory. -->
<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>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<IncludeInSso>false</IncludeInSso>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="verificationCode" Required="true"/>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="REST-EmailVerification"/>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingEmailAddress" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
But the related TextBox
is not rendered in the page.
Update 2: I found out why the text box is not rendered. It's related to the used ContentDefinition
. By using the api.selfasserted.profileupdate
content definition despite the api.localaccountpasswordreset
one, the field is displayed. Now I'm still working on it.
Update 3: I was able to make it work using the api.selfasserted.profileupdate
content definition. I'll post the complete solution as soon as I finish the integration with the validation APIs.
Upvotes: 2
Views: 2962
Reputation: 111
The solution was to use the api.selfasserted.profileupdate
content definition (instead of api.localaccountpasswordreset
) for the LocalAccountDiscoveryUsingEmailAddress
technical profile.
<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.selfasserted.profileupdate</Item>
<Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Your account has been locked. Contact your support person to unlock it, then try again.</Item>
<Item Key="EnforceEmailVerification">false</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="verificationCode" Required="true" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="REST-EmailVerification" />
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingEmailAddress" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
It looks mostly as a workaround, but it's the only option not to use the preview features of the display controls.
To further protect the validation, there is an API-side check in the REST-EmailVerification validation technical profile, that re-checks the code previously validated client-side:
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="REST-EmailVerification" />
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingEmailAddress" />
</ValidationTechnicalProfiles>
Additionally I'm currently adding a captcha to avoid abuses of the sending logics.
As soon as the display controls will be generally available, I'll reccomend my client to use them.
The reason why it didn't work with the password-reset-specific content definition, is that it doesn't support other custom fields:
https://learn.microsoft.com/it-it/azure/active-directory-b2c/contentdefinitions
Upvotes: 1
Reputation: 11335
Swap verified.email
output claim with the reference to your displayControl in the technical profile for password reset, which is LocalAccountDiscoveryUsingEmailAddress
.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/custom-email-sendgrid#make-a-reference-to-the-displaycontrol
Its essentially the exact same steps, except you make the "make a reference" change to the LocalAccountDiscoveryUsingEmailAddress
technical profile to show the display control on this specific page, which is referenced in Step 1 of the password reset journey to collect and verify the users email.
<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>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<IncludeInSso>false</IncludeInSso>
<DisplayClaims>
<DisplayClaim DisplayControlReferenceId="emailVerificationControl" />
</DisplayClaims>
<OutputClaims>
<!--<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />-->
<OutputClaim ClaimTypeReferenceId="email" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
And if you want a different email template for password reset compared to Sign Up, recreate a new displayControl and reference a different template.
Upvotes: 1