Invoke Rest API on email verification ADB2C custom policy - azure-ad-b2c

The default functionality of ADB2C for email verification verifies the verification code sent to the corresponding email.
However it does not check whether that email is already registered or not. Until now we have been doing this by calling a Rest API on clicking create. But now it is required to check if that email is already registered or not at the email verification step.
Is there a way to call a Rest API at email verification step? If yes, then how can it be done?

You could separate the input of email address and user data to a different page, and then just do the email verification on a next page. That way you can check if the user exists on the previous page and verify the email on the next.
You also do not need to call out to a REST endpoint to check if they exist. You can use a technical profile that inherits the AAD-Common Technical profile to see if they exist.
<TechnicalProfile Id="DoesUserExist">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" Required="true" PartnerClaimType="signInNames" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="UserSearchOID" PartnerClaimType="objectId" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>

Related

Any way to provide default values for inputs in an Azure AD B2C custom policy?

I am working on integrating Azure AD B2C into an existing shopping cart application, replacing an existing user identity solution.
I have already created a custom policy to implement user registration / sign-up and integrated it into the normal account creation process. However, I am having a problem with integrating registration during the checkout process.
With the old IDP, the checkout process first collects the user's name and email address. After that has been collected, the user is given the option to create an account with that information (if they are not already logged in). In order to avoid confusing double-entry of the email address, I would like to pass the email address that the user already entered to the B2C sign-up policy and have it fill in the email address input on the form.
Is there any way to do this? I don't find anything like this being addressed in the B2C documentation.
You can use a sign up policy with a claims resolver. Send the email in the query parameter. It’ll prepopulate the email into the text box.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/claim-resolver-overview
Based on the documentation linked by #JasSuri, I was able to come up with a solution.
To implement this, you modify the sign-up technical profile. You must add three things:
Add item IncludeClaimResolvingInClaimsHandling with value true to the metadata
Add a DefaultValue attribute to the email input claim with an appropriate claims resolver notation as the value
Add an AlwaysUseDefaultValue attribute to the email input claim with true as the value
I used an Oauth2 key-value claims resolver (which supports arbitrary query string parameters) and a query parameter named register_email.
The resulting technical profile looks like this:
<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>
<Item Key="IpAddressClaimReferenceId">IpAddress</Item>
<Item Key="ContentDefinitionReferenceId">api.localaccountsignup</Item>
<Item Key="language.button_continue">Create</Item>
<Item Key="IncludeClaimResolvingInClaimsHandling">true</Item> <!-- ADD THIS -->
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaims>
<!-- ADD DefaultValue AND AlwaysUseDefaultValue ATTRIBUTES BELOW -->
<InputClaim ClaimTypeReferenceId="email"
DefaultValue="{OAUTH-KV:register_email}"
AlwaysUseDefaultValue="true" />
</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" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonEmail" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>

Email verification box disappears when passing email from Facebook as input claim to self-asserted profile

We want to force users to provide and verify their email address even when they reject passing their email claim from Facebook.
We created an orchestration step which calls the following technical profile after coming back from Facebook:
<TechnicalProfile Id="SelfAsserted-ConfirmEmailSocial">
<DisplayName>Confirm email social</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>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
</OutputClaims>
</TechnicalProfile>
If the user rejects the facebook setting, the code works as expected: The verification control is shown.
However, if the user accepts the facebook setting, both the input field and the verification control disappear. (And the email gets pre-populated)
(Someone explains here stack overflow that the control is too dumb to realize that you want to verify the input claim https://stackoverflow.com/a/44429492/509464) but we haven't been able to get the suggested solution to work.
How can we force validation of emails provided in a previous orchestration step?
Update:
It seems we could create a workaround by making the input claim read-only somehow. (But only if it was empty).
Yes the only option is making it read only, since otherwise we assume the passed in email is already verified, which it is with Facebook. If you change the email, then you must verify it. You could modify the JavaScript/css to show the controls. Or in the custom policy, use an input claim transform to copy the email claim into a new read-only claim id. Then pre-populate the form using input claim. Then display the claim using output claim.

B2C: Localization for messages originating from REST API

I am currently implementing this example to use an rest API during the user registration.
The basic idea is that the API throws an 409 Conflict error to interrupt the registration.
// Can I return a special "StringId" or something here for localization?
return new ConflictObjectResult(new B2CResponseModel($"A verification email sent to you. Please open your mail box and click on the link. If you didn't receive the email, please click on the 'Send verification email' button.", HttpStatusCode.Conflict));
I want to show the user a message that is localized to their current language. I would prefer to do the localization within the custom policies, but I would also accept a solution within the API (would need to get the User Language for this).
Is there a way to do this localization? Like returning a StringId via API and using this within the policy?
I am also considering not returning an error from the API, to show the message in a new screen instead (like How to display error returned from custom REST API endpoint in a subsequent orchestration step?). However, localization options for this elude me as well.
In case anybody is looking for a way to send the user's locale to the REST API:
https://learn.microsoft.com/nb-no/azure/active-directory-b2c/claim-resolver-overview
<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://xxxx</Item>
<Item Key="AuthenticationType">None</Item>
<Item Key="SendClaimsIn">Body</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
<InputClaim ClaimTypeReferenceId="userLanguage" DefaultValue="{Culture:LanguageName}" />
<InputClaim ClaimTypeReferenceId="policyId" PartnerClaimType="policy" DefaultValue="{Policy:PolicyId}" />
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="{OIDC:scope}" />
<InputClaim ClaimTypeReferenceId="clientId" DefaultValue="{OIDC:ClientId}" />
</InputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
Hope this is similar to this
See the answer by Jas Suri. Pass the localisation parameter to API and return the localised message or can return an error code and based on that display translated message using policy itself.

In the PhoneFactor-InputOrVerify Technical Profile (B2C custom user flow), is it possible to allow the user to change their default mobile number?

In Azure B2C custom policies, our business process requirements mean we need control over whether:
The user may not replace the mobile number we have on record for them (i.e. first-time password set experience), or
We present the number we hold on record for them by default, but allow the user to overtype and save this number with a new number (e.g. logged-in edit profile experience).
The question: Is there a way I can allow the user to replace the number we hold on record for them with a new number, perhaps by the addition of a new button [Provide New Number] in this series:
[Send Code] [Call Me] [New Button To Provide New Number] [Cancel]
PhoneFactor-InputOrVerify sample screen (image)
The code below is a slight modification of the similarly-named policy from the B2C Custom Policy Starterpack.
Note how in this example we're providing the mobile phone number value stored in the mobile property of the user object as strongAuthenticationNumber. This is because there is currently no way of setting the MFA number programmatically. (Thanks for your help, Chris Padgett.)
Other than this, the policy is as per starterpack.
<ClaimsProvider>
<DisplayName>PhoneFactor</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="PhoneFactor-InputOrVerify">
<DisplayName>PhoneFactor</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.PhoneFactorProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ContentDefinitionReferenceId">api.phonefactor</Item>
<Item Key="ManualPhoneNumberEntryAllowed">true</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CreateUserIdForMFA" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="userIdForMFA" PartnerClaimType="UserId" />
<!--InputClaim ClaimTypeReferenceId="strongAuthenticationPhoneNumber" /-->
<InputClaim ClaimTypeReferenceId="mobile" PartnerClaimType="strongAuthenticationPhoneNumber" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="Verified.strongAuthenticationPhoneNumber" PartnerClaimType="Verified.OfficePhone" />
<OutputClaim ClaimTypeReferenceId="newPhoneNumberEntered" PartnerClaimType="newPhoneNumberEntered" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-MFA" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
I suspect there may be some metadata setting to enable a button or text input box as described, but to my knowledge there is no documentation for this yet.
You must create a custom flow for this that:
Prompts the end user to sign in with a local or external account.
Reads the current phone number for the end user from Azure AD.
Prompts the end user to verify their current phone number.
Prompts the end user to enter and verify their new phone number.
Writes the new phone number for the end user to Azure AD.
See this sample policy.

How can I read the mobile value from a B2C user record and transform it to a strongAuthenticationPhoneNumber?

I have the need to create Azure B2C user accounts programmatically. In a separate user data store I hold pertinent information about the users I need to set up in B2C including their mobile phone number, which we've already been communicating with them on.
My business requirement is that this mobile phone number is used as a secondary factor during the user's first-time login/password reset experience. I have an initial login experience which uses an externally-created JWT token to take the user to a custom User Journey where they can set a password for the first time.
I understand that it is not yet possible to set the Azure MFA mobile number via Graph API or PowerShell. (Is this still true?). Therefore B2C asks the user to enter their mobile number in the exemplar PhoneFactor-InputOrVerify Technical Profile. This is a security hole as you can just enter any mobile number in there and verify that number.
I can easily programmatically add the user's number to some other field - e.g. the mobile field on the user record.
Question 1.
Is there a way to read the user account mobile value and present it to a Technical Profile as if it is the strongAuthenticationPhoneNumber value or Verified.strongAuthenticationPhoneNumber?
Question 2.
Is this even a good idea? I imagine there are good reasons not to do this, but I can't fathom what they might be.
I've tried creating new ClaimTypes, reading the 'mobile' field value, creating ClaimsTranfromations to try to make the mobile claim appear to be the strongAuthenticationPhoneNumber claim, and trying generally to 'spoof' B2C into thinking this is the actual number as stored in the MFA data store.
This is the the standard PhoneFactor-InputOrVerify Technical Profile from the starterpack:
<ClaimsProvider>
<DisplayName>PhoneFactor</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="PhoneFactor-InputOrVerify">
<DisplayName>PhoneFactor</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.PhoneFactorProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ContentDefinitionReferenceId">api.phonefactor</Item>
<Item Key="ManualPhoneNumberEntryAllowed">true</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CreateUserIdForMFA" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="userIdForMFA" PartnerClaimType="UserId" />
<InputClaim ClaimTypeReferenceId="strongAuthenticationPhoneNumber" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="Verified.strongAuthenticationPhoneNumber" PartnerClaimType="Verified.OfficePhone" />
<OutputClaim ClaimTypeReferenceId="newPhoneNumberEntered" PartnerClaimType="newPhoneNumberEntered" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-MFA" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
I can provide more code samples of the custom User Journey I mentioned earlier but I don't think this will help with this problem.
You have a few options:
You can add the strongAuthenticationPhoneNumber claim to the same JWT that is used for the onboarding flow and prompt for verification of this phone number during this onboarding flow.
You can map to the strongAuthenticationPhoneNumber claim for the PhoneFactor-InputOrVerify technical profile from the mobile property (or an extension property) of the user object.
For option 1, the onboarding user journey should write the verified phone number to the user object, without the newPhoneNumberEntered-based precondition:
<OrchestrationStep Order="8" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserWriteWithObjectId" TechnicalProfileReferenceId="AAD-UserWritePhoneNumberUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
For option 2, you can map to the strongAuthenticationPhoneNumber claim from the mobile property, as follows:
<InputClaims>
<InputClaim ClaimTypeReferenceId="userIdForMFA" PartnerClaimType="UserId" />
<InputClaim ClaimTypeReferenceId="mobile" PartnerClaimType="strongAuthenticationPhoneNumber" />
</InputClaims>

Resources