Reputation: 41
First orchestration step is login- taking email output claim Second step is email verify- I want to populate email from login and verify with email OTP.
Problem- If email populated, verify button does not come. If verify button comes, email not populated.
Upvotes: 4
Views: 2051
Reputation: 151
I was able to solve for this use case by using a DisplayControl within a DisplayClaim instead of using the Verified.Email in an OutputClaim.
First define these two attributes in your BuildingBlocks, ClaimsSchema:
<BuildingBlocks>
<ClaimsSchema>
<ClaimType Id="readOnlyEmail">
<DisplayName>Email</DisplayName>
<DataType>string</DataType>
<UserInputType>Readonly</UserInputType>
</ClaimType>
<ClaimType Id="verificationCode">
<DisplayName>Secondary Verification Code</DisplayName>
<DataType>string</DataType>
<UserHelpText>Enter your email verification code</UserHelpText>
<UserInputType>TextBox</UserInputType>
</ClaimType>
<!-- Other claims you have defined -->
</ClaimsSchema>
</BuildingBlocks>
We are going to copy the email attribute into the readOnlyEmail attribute so we can display it on the screen, and the verificationCode attribute is used when validating the email one-time-passcode (OTP). Stick with me here.
Define a ClaimsTransformation inside of BuildingBlocks, ClaimsTransformations that copies over the email into readOnlyEmail.
<BuildingBlocks>
<!-- ClaimsSchema -->
<ClaimsTransformations>
<ClaimsTransformation Id="CopyEmailAddress" TransformationMethod="CopyClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" TransformationClaimType="inputClaim"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="readOnlyEmail" TransformationClaimType="outputClaim"/>
</OutputClaims>
</ClaimsTransformation>
</ClaimsTransformations>
<!-- ContentDefinitions, DisplayControls, etc. -->
</BuildingBlocks>
Modify the DataUri element in the ContentDefinition for api.selfasserted to make it version 2.0.0 and include the "contract" tag. You must do this to allow DisplayClaims to work.
The api.selfasserted ContentDefinition should now look like this:
<BuildingBlocks>
<!-- ClaimsSchema, ClaimsTransformations -->
<ContentDefinitions>
<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.0.0</DataUri>
<Metadata>
<Item Key="DisplayName">Collect information from user page</Item>
</Metadata>
</ContentDefinition>
</ContentDefinitions>
<!-- DisplayControls, etc. -->
</BuildingBlocks>
The only change here is to the DataUri element in the ContentDefinition. Notice that we updated it to include the word "contract" and it's version is now 2.0.0.
Then define a DisplayControl inside of BuildingBlocks, DisplayControls that uses our readOnlyEmail, and verficationCode ClaimTypes:
<BuildingBlocks>
<!-- ClaimsSchema, ClaimsTransformations, ContentDefinitions, etc -->
<DisplayControls>
<DisplayControl Id="emailVerificationControl" UserInterfaceControlType="VerificationControl">
<InputClaims>
<InputClaim ClaimTypeReferenceId="readOnlyEmail" />
</InputClaims>
<DisplayClaims>
<DisplayClaim ClaimTypeReferenceId="readOnlyEmail" />
<DisplayClaim ClaimTypeReferenceId="verificationCode" ControlClaimType="VerificationCode" />
</DisplayClaims>
<Actions>
<Action Id="SendCode">
<ValidationClaimsExchange>
<ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="AadSspr-SendCode" />
</ValidationClaimsExchange>
</Action>
<Action Id="VerifyCode">
<ValidationClaimsExchange>
<ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="AadSspr-VerifyCode" />
</ValidationClaimsExchange>
</Action>
</Actions>
</DisplayControl>
</DisplayControls>
</BuildingBlocks>
Notice here we are displaying readOnlyEmail and the second DisplayClaim for verificationCode is for collecting the OTP once it's been sent to your email.
Also notice the Actions which point to TechnicalProfiles we have yet to define. They correspond to buttons on the screen and will send the email and verify the code. Let's define those next. You can put them in whatever ClaimsProvider you'd like:
<TechnicalProfile Id="AadSspr-SendCode">
<DisplayName>Send Code</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.AadSsprProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="Operation">SendCode</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="readOnlyEmail" PartnerClaimType="emailAddress"/>
</InputClaims>
</TechnicalProfile>
<TechnicalProfile Id="AadSspr-VerifyCode">
<DisplayName>Verify Code</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.AadSsprProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="Operation">VerifyCode</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="verificationCode" PartnerClaimType="verificationCode" />
<InputClaim ClaimTypeReferenceId="readOnlyEmail" PartnerClaimType="emailAddress"/>
</InputClaims>
</TechnicalProfile>
These TechnicalProfiles are taking advantage of the AadSsprProtocolProvider to send and verify the code. MS has good documentation here: https://learn.microsoft.com/en-us/azure/active-directory-b2c/aad-sspr-technical-profile
Almost there. Now let's create a SelfAssertedAttributeProvider TechnicalProfile to take advantage of our new DisplayClaim that will do the inline verification.
Create this TechnicalProfile in whatever ClaimsProvider you'd like:
<TechnicalProfile Id="SelfAsserted-VerifyEmail">
<DisplayName>Verify Email</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>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CopyEmailAddress" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
<InputClaim ClaimTypeReferenceId="readOnlyEmail" />
</InputClaims>
<DisplayClaims>
<DisplayClaim DisplayControlReferenceId="emailVerificationControl" />
</DisplayClaims>
</TechnicalProfile>
This TechnicalProfile will take email as the input, copy it to our readOnlyEmail with the InputClaimsTransformation, and then the readOnlyEmail is used in our new emailVerificationControl DisplayClaim. That emailVerficationControl then shows the user a readOnly value of the email, has a button to send the code, and then the ability to verify it once it's been sent.
The last thing you need to do is use this new TechnicalProfile in your UserJourney, OrchestrationSteps like so:
<OrchestrationStep Order="X" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="SelfAsserted-VerifyEmail-CE" TechnicalProfileReferenceId="SelfAsserted-VerifyEmail"/>
</ClaimsExchanges>
</OrchestrationStep>
This technique solves the use case you were having trouble with.
Upvotes: 6
Reputation: 58823
We faced this same issue. Needed to automatically fill in the email but also verify it. The issue is that the value that is filled in is considered verified right away.
So we had to do it a bit indirectly. First we defined an "originalEmail" claim:
<ClaimType Id="originalEmail">
<DisplayName>Original email</DisplayName>
<DataType>string</DataType>
<UserInputType>Readonly</UserInputType>
</ClaimType>
The signup technical profile then defines 2 input claims, 2 input transformations and 1 output claim (along with all the other stuff):
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="SetOriginalEmailFromEmail" />
<InputClaimsTransformation ReferenceId="SetEmailToNull" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
<InputClaim ClaimTypeReferenceId="originalEmail" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="originalEmail" />
</OutputClaims>
The first claim transformation copies the value from email to originalEmail:
<ClaimsTransformation Id="SetOriginalEmailFromEmail" TransformationMethod="FormatStringClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" TransformationClaimType="inputClaim" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringFormat" DataType="string" Value="{0}" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="originalEmail" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
And the second transformation resets the email claim:
<ClaimsTransformation Id="SetEmailToNull" TransformationMethod="NullClaim">
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" TransformationClaimType="claim_to_null" />
</OutputClaims>
</ClaimsTransformation>
This way the email will show up empty at first, with the original email in a hidden field next to it. You can then use JavaScript in a custom UI to copy the value over at load:
(function () {
setTimeout(function () {
copyOriginalEmailToEmail();
}, 500);
function copyOriginalEmailToEmail() {
var originalEmailField = document.getElementById('originalEmail');
var originalEmail = originalEmailField && originalEmailField.value;
if (originalEmail) {
document.getElementById('email').value = originalEmail;
}
}
}());
It's quite a hack but it has worked quite well. Setting the value immediately causes the Send code button to disappear as well, so that's why the delay is there.
If there is an easier way to do this, I'd like to know too :D
Upvotes: 0