Can Azure-AD B2C use a mobile telephone number as a username?
This can be implemented as a custom policy, from the SocialAndLocalAccountsWithMfa starter pack where the end-user's phone number is stored as a sign-in name, with the following changes.
1) Create a custom attribute called PhoneVerified of type Boolean to represent whether the end-user's phone number has been verified.
2) In the TrustFrameworkBase.xml file, add the following claim types to the claims schema:
i. The phone claim type to represent how the end-user's phone number is entered. E.164 is the required format of this claim type:
<ClaimType Id="phone">
<DisplayName>Phone Number</DisplayName>
<DataType>string</DataType>
<UserInputType>TextBox</UserInputType>
<Restriction>
<Pattern RegularExpression="^\+[0-9]{7,15}$" HelpText="Please enter a valid phone number." />
</Restriction>
</ClaimType>
ii. The signInNames.phoneNumber claim type to represent how the end-user's phone number is saved:
<ClaimType Id="signInNames.phoneNumber">
<DisplayName>Phone Number</DisplayName>
<DataType>string</DataType>
<UserInputType>TextBox</UserInputType>
</ClaimType>
iii. The extension_PhoneVerified claim type to represent whether the end-user's phone number has been verified:
<ClaimType Id="extension_PhoneVerified">
<DisplayName>Phone Number Verified</DisplayName>
<DataType>boolean</DataType>
</ClaimType>
3) In the TrustFrameworkBase.xml file, add the LocalAccountSignUpWithLogonPhone tehcnical profile to the Local Account claims provider and AAD-UserWriteUsingLogonPhone technical profile to the Azure Active Directory claims provider, for registering a new end-user with a phone number:
<TechnicalProfile Id="LocalAccountSignUpWithLogonPhone">
<DisplayName>Phone 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>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="phone" 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" />
<!-- Optional claims, to be collected from the user -->
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surname" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonPhone" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
<TechnicalProfile Id="AAD-UserWriteUsingLogonPhone">
<Metadata>
<Item Key="Operation">Write</Item>
<Item Key="RaiseErrorIfClaimsPrincipalAlreadyExists">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="phone" PartnerClaimType="signInNames.phoneNumber" Required="true" />
</InputClaims>
<PersistedClaims>
<!-- Required claims -->
<PersistedClaim ClaimTypeReferenceId="phone" PartnerClaimType="signInNames.phoneNumber" />
<PersistedClaim ClaimTypeReferenceId="newPassword" PartnerClaimType="password"/>
<PersistedClaim ClaimTypeReferenceId="displayName" DefaultValue="unknown" />
<PersistedClaim ClaimTypeReferenceId="passwordPolicies" DefaultValue="DisablePasswordExpiration" />
<PersistedClaim ClaimTypeReferenceId="extension_PhoneVerified" DefaultValue="false" AlwaysUseDefaultValue="true" />
<!-- Optional claims. -->
<PersistedClaim ClaimTypeReferenceId="givenName" />
<PersistedClaim ClaimTypeReferenceId="surname" />
</PersistedClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="newUser" PartnerClaimType="newClaimsPrincipalCreated" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="signInNames.phoneNumber" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
The end-user's phone number is saved as a sign-in name of type phoneNumber and whether the end-user's phone number has been verified is set to false.
4) In the TrustFrameworkBase.xml file, add a SelfAsserted-LocalAccountSignin-Phone technical profile to the Local Account claims provider, for logging in an existing end-user with a phone number:
<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Phone">
<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="SignUpTarget">SignUpWithLogonPhoneExchange</Item>
<Item Key="setting.operatingMode">Username</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>
The setting.operatingMode setting is set to Username so that the logon identifier field doesn't have the required format of an email address.
5) In the TrustFrameworkBase.xml file, add a AAD-UserReadForPhoneUsingObjectId technical to the Azure Active Directory claims provider, for getting the end-user's object including the phone profile:
<TechnicalProfile Id="AAD-UserReadForPhoneUsingObjectId">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInNames.phoneNumber" />
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surname" />
<OutputClaim ClaimTypeReferenceId="extension_PhoneVerified" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
6) In the TrustFrameworkBase.xml file, add a PhoneFactor-Verify technical profile to the Phone Factor claims provider, for verifying the end-user's phone number:
<TechnicalProfile Id="PhoneFactor-Verify">
<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">false</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="signInNames.phoneNumber" PartnerClaimType="strongAuthenticationPhoneNumber" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInNames.phoneNumber" PartnerClaimType="Verified.strongAuthenticationPhoneNumber" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-MFA" />
</TechnicalProfile>
7) In the the TrustFrameworkBase.xml file, add an UserWritePhoneVerifiedUsingObjectId technical profile to the Azure Active Directory claims provider, for setting whether the end-user's phone number has been verified to true:
<TechnicalProfile Id="AAD-UserWritePhoneNumberUsingObjectId">
<Metadata>
<Item Key="Operation">Write</Item>
<Item Key="RaiseErrorIfClaimsPrincipalAlreadyExists">false</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true" />
</InputClaims>
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="objectId" />
<PersistedClaim ClaimTypeReferenceId="extension_PhoneVerified" DefaultValue="true" AlwaysUseDefaultValue="true" />
</PersistedClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
Note: Additional technical profiles must be added in the TrustFrameworkBase.xml file to allow an existing end-user to reset their current password using a phone number but this has been left as an exercise for the reader.
8) In the TrustFrameworkBase.xml file, add a SignUpOrSignInForPhone user journey, which allows either a new end-user to register with a phone number or an existing end-user to log in with a phone number and then verifies the end-user's phone number.
<UserJourney Id="SignUpOrSignInForPhone">
<OrchestrationSteps>
<!-- Display the sign-up or sign-in interaction so an existing end-user can sign in with a phone number -->
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninPhoneExchange" />
</ClaimsProviderSelections>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninPhoneExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Phone" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- A new end-user has selected to sign up with a phone number -->
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SignUpWithLogonPhoneExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonPhone" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Read the user object -->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadForPhoneUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- If the end-user's phone number hasn't been verified, then verify it during sign-up or following the first sign-in with an unverified phone number -->
<OrchestrationStep Order="4" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>extension_PhoneVerified</Value>
<Value>True</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>isActiveMFASession</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="PhoneFactor-Verify" TechnicalProfileReferenceId="PhoneFactor-Verify" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Set whether the end-user's phone number has been verified to true -->
<OrchestrationStep Order="5" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>extension_PhoneVerified</Value>
<Value>True</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>isActiveMFASession</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserWriteWithObjectId" TechnicalProfileReferenceId="AAD-UserWritePhoneVerifiedUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="6" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
</UserJourney>
9) Create a relying party file called SignUpOrSignInForPhone.xml (or similar) and reference the SignUpOrSignInForPhone user journey:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<TrustFrameworkPolicy
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06"
PolicySchemaVersion="0.3.0.0"
TenantId="yourtenant.onmicrosoft.com"
PolicyId="B2C_1A_signup_signin_phone"
PublicPolicyUri="http://yourtenant.onmicrosoft.com/B2C_1A_signup_signin_phone">
<BasePolicy>
<TenantId>yourtenant.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId>
</BasePolicy>
<RelyingParty>
<DefaultUserJourney ReferenceId="SignUpOrSignInForPhone" />
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" />
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surname" />
<OutputClaim ClaimTypeReferenceId="signInNames.phoneNumber" PartnerClaimType="phone_number" />
<OutputClaim ClaimTypeReferenceId="extension_PhoneNumber" PartnerClaimType="phone_number_verified" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
</TrustFrameworkPolicy>
The token claims that are output to a relying party include:
i. The phone_number claim that represents the end-user's phone number.
ii. The phone_number_verified claims that represents whether the end-user's phone number has been verified.
It is finally supported officially:
Azure AD B2C now supports phone-based sign-in and sign-up for apps using B2C custom policy