Custom Email verification in a single orchestration step in AD B2C Custom policies - azure-ad-b2c

I have a multi-step custom policy that first collects email from user and sends a verification code to the user when user clicks continue. The journey works fine. But the thing is validation of code is happening in next step. I need to bring that code validation in to the first orchestration step. I'm following the below doc to implement this journey:
"https://github.com/yoelhor/aadb2c-verification-code"
My technical profile is like the following:
<TechnicalProfile Id="LocalAccountSignUpWithLogonEmail-FirstStep">
<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">Send verification email</Item>
<Item Key="EnforceEmailVerification">False</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
<OutputClaim ClaimTypeReferenceId="verificationCode" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CopyEmailAsReadOnly" />
</OutputClaimsTransformations>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="REST-API-SendVerificationEmail" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
and the validation tech profile as follows:
<TechnicalProfile Id="REST-API-SendVerificationEmail">
<DisplayName>Sign-Up send link</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://myweb.azurewebsites.net/api/Identity/SendVerificationCode</Item>
<Item Key="AuthenticationType">None</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="AllowInsecureAuthInProduction">true</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="verificationCode" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>

It cant be achieved currently, to call the REST API, the form must be submitted and that will cause the orchestration step to complete and move to the next one.
In a few weeks we will release how to achieve this, and without the use of an external REST API to generate and verify the OTP Codes. Stay tuned.

Related

Why doesn't FormatLocalizedString work in Azure AD B2C custom policies?

I need to display a custom message after the code is sent in the password reset flow, I have tried following this https://learn.microsoft.com/en-us/azure/active-directory-b2c/string-transformations#formatlocalizedstring but it does not work. I am doing the following, very similar to the reference.
I add the same claimTransformation
<ClaimsTransformation Id="SetResponseMessageForOTPSent" TransformationMethod="FormatLocalizedString">
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringFormatId" DataType="string" Value="ResponseMessage_EmailExists" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="responseMsg" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
And the localized string
<LocalizedString ElementType="FormatLocalizedStringTransformationClaimType" StringId="ResponseMessage_EmailExists">Code sent to "{0}"</LocalizedString>
Finally I refer to the claimTransformation from a technicalProfile
<TechnicalProfile Id="SendOtp">
<DisplayName>Send Otp</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ServiceUrl">Url</Item>
<Item Key="AuthenticationType">ApiKeyHeader</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="ContentDefinitionReferenceId">api.localaccountpasswordreset</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
</InputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="SetResponseMessageForOTPSent" />
</OutputClaimsTransformations>
</TechnicalProfile>
But it doesn't work, it always shows the message "Unable to validate the information provided". What am I doing wrong?

Calling secure REST API from Azure B2C custom policy to embed claims

I am trying to consume a Azure B2C secured API as part of the user journey by creating custom policies. I have created a claims provider to procure a bearer token as below
<ClaimsProvider>
<DisplayName>REST APIs</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="SecureREST-AccessToken">
<DisplayName></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://***.b2clogin.com/***.onmicrosoft.com/B2C_1A_SignUpOrSignIn/oauth2/v2.0/authorize</Item>
<Item Key="AuthenticationType">Basic</Item>
<Item Key="SendClaimsIn">Form</Item>
</Metadata>
<CryptographicKeys>
<Key Id="BasicAuthenticationUsername" StorageReferenceId="B2C_1A_SecureRESTClientId" />
<Key Id="BasicAuthenticationPassword" StorageReferenceId="B2C_1A_SecureRESTClientSecret" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="grant_type" DefaultValue="client_credentials" />
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="https://***.onmicrosoft.com/profileapi/profileapi-scope" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="bearerToken" PartnerClaimType="access_token" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
And another claims provider to call my secure REST API as below
<ClaimsProvider>
<DisplayName>REST APIs</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="AzureFunctions-GetRole">
<DisplayName>Get Roles </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://***.azurewebsites.net/api/UserProfiles/CheckAdminUser</Item>
<Item Key="AuthenticationType">Bearer</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="AllowInsecureAuthInProduction">false</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
<InputClaim ClaimTypeReferenceId="signInNames.emailAddress" PartnerClaimType="email" />
<InputClaim ClaimTypeReferenceId="bearerToken"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="IsAdminUser" PartnerClaimType="IsAdminUser" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
How do I tie these two up? Should these be two steps in the user journey?
AAD B2C endpoint doesn’t support client credentials flow. Your initial call to get a token should model AAD client credentials flow:
https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
You would call these technical profiles from the user journey if they return no possibility of an error to the user. Or otherwise as validation technical profiles referenced from a self asserted technical profile.

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

Calling Rest Api from Custom Policy

I'm having problems calling a rest api from a custom policy. I need the data sent into the rest api like the following
{
"correlationId": "123456",
"message": {
"email": "test#somedomain.com"
}
}
I have the following Claims Transformation
<ClaimsTransformation Id="GenerateGetAadRequestBody" TransformationMethod="GenerateJson">
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" TransformationClaimType="message.emailAddress" />
<InputClaim ClaimTypeReferenceId="correlationId" TransformationClaimType="correlationId" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="getAadRequestBody" TransformationClaimType="outputClaim"/>
</OutputClaims>
</ClaimsTransformation>
And I'm making the Rest Api call using the following Claims Provider
<ClaimsProvider>
<DisplayName>Custom REST API</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="RestApiGetAad">
<DisplayName>Call the Rest API</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ServiceUrl">{Settings:RestApiGetAadUrl}</Item>
<Item Key="AuthenticationType">Basic</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="ClaimUsedForRequestPayload">getAadRequestBody</Item>
<Item Key="ResolveJsonPathsInJsonTokens">true</Item>
<Item Key="DebugMode">{Settings:RestDebugMode}</Item>
<Item Key="DefaultUserMessageIfRequestFailed">Cannot process your request right now, please try again later.</Item>
</Metadata>
<CryptographicKeys>
<Key Id="BasicAuthenticationUsername" StorageReferenceId="{Settings:RestApiUserName}" />
<Key Id="BasicAuthenticationPassword" StorageReferenceId="{Settings:RestApiPassword}" />
</CryptographicKeys>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="GenerateGetAadRequestBody" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="getAadRequestBody" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="useAAD" PartnerClaimType="content.UseAad" Required="true" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
It looks like the call is being made, however, there is nothing being sent in the body. Any idea what I'm doing wrong?
Unfortunatly, this was issuing the wrong error code. Should have been PEBCAK, as I did not have the claims that I was trying to use in the ClaimsTransformation. Think that posting here gave me a bit of Rubber Duck Debugging, and I walked the flow again spotting the issue

How to validate entered value is same as pre-defined value?

I am trying to verify that an email verification code entered by a user is same as one returned earlier by another technical profile. This journey step works as expected when the user enters the correct code but hangs when the user enters an incorrect code. What am I doing wrong. Here is the technical policy executed by my journey step:
<TechnicalProfile Id="EnterEmailVerifyCode">
<DisplayName>Email entry</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">Continue</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
<InputClaim ClaimTypeReferenceId="emailVerificationCodeEntered" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="emailVerificationCodeEntered" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="AssertEmailVerificationCodesAreEqual" />
</OutputClaimsTransformations>
</TechnicalProfile>
The self-asserted technical profile must invoke the claims transformation as a validation technical profile.
Declare a claims transformation technical profile that invokes the claims transformation:
<TechnicalProfile Id="ValidateEmailVerificationCodes">
<DisplayName>Validate Email Verification Codes</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<InputClaims>
<InputClaim ClaimTypeReferenceId="emailVerificationCodeEntered" />
<InputClaim ClaimTypeReferenceId="emailVerificationCodeGenerated" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="emailVerificationCodeEntered" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="AssertEmailVerificationCodesAreEqual" />
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
Then invoke this claims transformation technical profile from the self-asserted technical profile as a validation technical profile:
<TechnicalProfile Id="EnterEmailVerifyCode">
...
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
...
<Item Key="UserMessageIfClaimsTransformationStringsAreNotEqual">The verification code is invalid</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
<InputClaim ClaimTypeReferenceId="emailVerificationCodeEntered" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="emailVerificationCodeEntered" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="ValidateEmailVerificationCodes" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
UserMessageIfClaimsTransformationStringsAreNotEqual specifies the error message that is shown if the claims transformation raises errors.

Resources