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

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.

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>

In Azure B2C Custom Policies, how do I display more than 2 distinct validation messages for validations that depend on Validation Technical Profiles?

Scenario
In my sign up flow, I have the requirement to show:
an email address field and a confirm email address field
a password field and a confirm password field
I have to validate that:
the text entered in the email address field matches the text entered in the confirm email address field
the text entered in the password field matches the text entered in the confirm password field
the email address entered for a user doesn't already exist in Azure B2C
These validations should display in the existing form, rather than a new page.
The only way I've been able to display validation messages inline as a result of the output from Validation Technical Profiles, is by using the two built in asserts, AssertStringClaimsAreEqual (for the email and password comparisons) and AssertBooleanClaimIsEqualToValue (for the existing user check). These cause messages, defined in the calling Claims Provider Technical Profile, to be displayed when the assert fails.
Problem
I have three error cases that require distinct messages, and I'm only able to define and display messages for two. Currently, I'm working around this by displaying "Email and confirm email, or password and confirm password do not match." for both the failed text comparisons, and "This email address is already in use, please sign in." for the existing user case. The existing user case is fine, but I would describe the single text comparison message as 'Okay, but doesn't really meet the acceptance criteria' and 'I can't believe there's not a better way to do this'.
Any help would be gratefully received at this point.
Current Code Snippets
The relevant Validation Technical Profiles:
<TechnicalProfile Id="EmailMatchValidator">
<DisplayName>Check if email and confirm email match</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="DoEmailsMatch"/>
</InputClaimsTransformations>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="emailsMatch" Required="true"/>
</OutputClaims>
</TechnicalProfile>
<TechnicalProfile Id="PasswordMatchValidator">
<DisplayName>Check if password and confirm password match</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="DoPasswordsMatch"/>
</InputClaimsTransformations>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="passwordsMatch" Required="true"/>
</OutputClaims>
</TechnicalProfile>
<TechnicalProfile Id="UserExistsValidator">
<DisplayName>Asset if user exists</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<InputClaims>
<InputClaim ClaimTypeReferenceId="userExists"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="userExists" Required="true"/>
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="DoesUserExist"/>
</OutputClaimsTransformations>
</TechnicalProfile>
These Technical Profiles reference Output Claims Transformations, which look like this:
<ClaimsTransformation Id="DoEmailsMatch" TransformationMethod="AssertStringClaimsAreEqual">
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" TransformationClaimType="inputClaim1"/>
<InputClaim ClaimTypeReferenceId="confirmEmail" TransformationClaimType="inputClaim2"/>
</InputClaims>
<InputParameters>
<InputParameter Id="stringComparison" DataType="string" Value="ordinalIgnoreCase"/>
</InputParameters>
</ClaimsTransformation>
<ClaimsTransformation Id="DoPasswordsMatch" TransformationMethod="AssertStringClaimsAreEqual">
<InputClaims>
<InputClaim ClaimTypeReferenceId="password" TransformationClaimType="inputClaim1"/>
<InputClaim ClaimTypeReferenceId="confirmpassword" TransformationClaimType="inputClaim2"/>
</InputClaims>
<InputParameters>
<InputParameter Id="stringComparison" DataType="string" Value="ordinalIgnoreCase"/>
</InputParameters>
</ClaimsTransformation>
<ClaimsTransformation Id="DoesUserExist" TransformationMethod="AssertBooleanClaimIsEqualToValue">
<InputClaims>
<InputClaim ClaimTypeReferenceId="userExists" TransformationClaimType="inputClaim"/>
</InputClaims>
<InputParameters>
<InputParameter Id="valueToCompareTo" DataType="boolean" Value="false"/>
</InputParameters>
</ClaimsTransformation>
DoEmailsMatch and DoPasswordsMatch are straightforward enough, using the AssertStringClaimsAreEqual built-in assert to display a message defined in the Metadata of the calling Claim Provider Technical Profile:
<Item Key="UserMessageIfClaimsTransformationStringsAreNotEqual">Email and confirm email, or password and confirm password do not match.</Item>
DoesUserExist relies on AssertBooleanClaimIsEqualToValue, which means it needs a boolean to work with, to decide whether to display the other message defined in the Metadata:
<Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">This email address is already in use, please sign in.</Item>
The code that sets that boolean is probably out of scope for this question, but in short, it gets set by a read on the AAD database that returns a matching User Principal Name in a claim, or no claim at all. This undergoes a Claim Transformation using DoesClaimExist to set the boolean userExists claim, used by DoesUserExist above.
For the third one, do an AAD Read operation instead, and it has a built in error message for when the use exists.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/active-directory-technical-profile#ui-elements
You can find this used in the AAD B2C starter pack for the default sign up flow.
For password matching, use password and reenter password, use the built in way
https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack/blob/master/LocalAccounts/TrustFrameworkBase.xml#L648
Now you should only need the 1 claims transform for email matching.

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>

Azure B2C AD: Is the email address mandatory for Local Accounts identity providers based on Username?

Am I missing something? It does not seem to be possible to set it up so that the email address is not required (or not even prompted for at all) during sign-up? You can only disable the email verification.
We may not allow the users to enter an email address at all. They must register with a username only. Surely this must be supported?
I have redone the entire process over from scratch twice now following all the documentation I could find on the topic. But the outcome remains the same. I start by creating a new B2C tenant and I ensure that under Identity Providers, only "Username" is selected as the "Local Accounts". Then I go to "Sign-up or sign-in policies" and create a custom template, then click on edit. Then I ensure that the Identity Providers is set to only the "User ID signup" (and Local Account), and that in the Sign-up Attributes as well as in the Application Claims I do not have the "Email Address" selected. Then I go to "Page UI Customization" and click on "Local account sign-up page". I enter my custom URL. Under the "Sign-up attributes" it lists "Email Address". Email address should not be there AT ALL. When I click on Email Address there is only an option to set "Require verification" to No. The Optional toggle switch is DISABLED. So I can't even make it optional.
The main point here is that when I use "Username" instead of "Email" as the Identity Provider, it should most definitely not force an email address on me.
If you try the above steps in the Azure Portal as it stands today 8 Aug 2018 I am sure you will find the same restriction. This seems like a bug to me, maybe it slipped in somewhere along the line?
Yes it is possible to register the user in B2C without email using custom policies.
You can follow the steps mention here to create custom policies and download the starter kit which has examples of how the policies can be modified to suit your requirement.
Below is the TechnicalProfile which i have used to SignUp user without email being mandatory
<TechnicalProfile Id="LocalAccountSignUpWithLogonName">
<DisplayName>User ID 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="LocalAccountType">Username</Item>
<Item Key="LocalAccountProfile">true</Item>
<Item Key="language.button_continue">Create</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" Required="true" />
<OutputClaim ClaimTypeReferenceId="signInName" Required="true" />
<OutputClaim ClaimTypeReferenceId="email" />
<OutputClaim ClaimTypeReferenceId="newPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="jobTitle" />
<OutputClaim ClaimTypeReferenceId="postalCode" />
<OutputClaim ClaimTypeReferenceId="city" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surName" />
<OutputClaim ClaimTypeReferenceId="executed-SelfAsserted-Input" DefaultValue="true" />
<OutputClaim ClaimTypeReferenceId="newUser" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonName" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
The toggle switch that allows you to select between username-based or email-based account is to indicate which of these will be used for sign-in. However, this does not mean that in a username-based account, the email will not be collected - it's just that email cannot be used to sign-in.
Regardless, the email address in basic policies is required to support password reset user journeys even for username-based accounts. If B2C does not collect the email address, then a user cannot reset their password even if they forgot it.
If you really want to not collect email, then your only option today is to use custom policies. In that case, you will have to determine how you want to support password reset, if at all.

ad b2c allow user to change MFA setting

how would I allow the user to change their MFA setting like their phone number? I don't see any option for them to be able to do that easily.
Try this. B2C MFA reset was the reason I wrote it.
http://gordon.byers.me/azure/resetting-a-users-azure-ad-multi-factor-mfa-requirement/
As it's powershell you could put it inside an Azure function and call it via HTTP to allow the user to self serve.
Currently it is not possible to change an Azure AD B2C users' MFA settings.
There's already an ask for this ask in the Azure AD B2C forum that you should vote for:
https://feedback.azure.com/forums/169401-azure-active-directory/suggestions/15334329-change-security-info
The MFA phone number can be changed with custom policies. When you create a UserJourney that invokes a TechnicalProfile that does not take the strongAuthenticationPhoneNumber as InputClaim, IEF acts as if the user registers for MFA for the first time.
Of course you need to think about security measures, since it is a second factor that the user changes (e.g. ask the user to input some data, that can be validated, before allowing the user to change the phonenumber). Otherwise the use of MFA makes no sense.
Apparently, user's can't do that as stated by Saca 😢
Admins 👮 can do this on the user's behalf via the Azure Portal though:
Users -> All users -> Pick the user you're interested in -> Update Phone under Authentication contact info
Looks like this is now possible with custom policies.
There is a full example here: https://github.com/azure-ad-b2c/samples/tree/master/policies/edit-mfa-phone-number
In case the link breaks, the key part appears to be this:
<TechnicalProfiles>
<TechnicalProfile Id="PhoneFactor-EditAndVerify">
<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" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="Verified.strongAuthenticationPhoneNumber" PartnerClaimType="Verified.OfficePhone" />
<OutputClaim ClaimTypeReferenceId="newPhoneNumberEntered" PartnerClaimType="newPhoneNumberEntered" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>

Resources