Azure Active Directory B2C (Azure AD B2C) integrates directly with Azure AD Multi-Factor Authentication so that you can add a second layer of security to sign-up and sign-in experiences in your applications. For more information, see Enable multi-factor authentication in Azure Active Directory B2C
This article gives an overview of the local and social accounts sign-up or sign-in with MFA user journey custom policies. We recommend you to check out the Local and social accounts sign-up or sign-in user journey before reading this article.
The SocialAndLocalAccountsWithMfa starter pack relies on the SocialAndLocalAccounts. The following are the elements that you have to add to your policy to support MFA.
A claim provides a temporary storage of data during an Azure AD B2C policy execution. The claims schema is the place where you declare your claims. The following elements are used to define the claim:
<!--
<BuildingBlocks>
<ClaimsSchema> -->
<ClaimType Id="strongAuthenticationPhoneNumber">
<DisplayName>Phone Number</DisplayName>
<DataType>string</DataType>
<Mask Type="Simple">XXX-XXX-</Mask>
<UserHelpText>Your telephone number</UserHelpText>
</ClaimType>
<ClaimType Id="Verified.strongAuthenticationPhoneNumber">
<DisplayName>Verified Phone Number</DisplayName>
<DataType>string</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OpenIdConnect" PartnerClaimType="phone_number" />
</DefaultPartnerClaimTypes>
<Mask Type="Simple">XXX-XXX-</Mask>
<UserHelpText>Your office phone number that has been verified</UserHelpText>
</ClaimType>
<ClaimType Id="newPhoneNumberEntered">
<DisplayName>New Phone Number Entered</DisplayName>
<DataType>boolean</DataType>
</ClaimType>
<ClaimType Id="userIdForMFA">
<DisplayName>UserId for MFA</DisplayName>
<DataType>string</DataType>
</ClaimType>
<!--
</ClaimsSchema>
</BuildingBlocks> -->
The CreateUserIdForMFA claims transformation creates a unique identifier for the user. The identifier is used when Azure AD B2C sends and verifies the code.
<!--
<BuildingBlocks>
<ClaimsTransformations> -->
<ClaimsTransformation Id="CreateUserIdForMFA" TransformationMethod="FormatStringClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" TransformationClaimType="inputClaim" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringFormat" DataType="string" Value="{0}@{RelyingPartyTenantId}" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="userIdForMFA" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
<!--
</ClaimsTransformations>
</BuildingBlocks> -->
The following content definition is used to render the MFA registration and verification.
<!--
<BuildingBlocks>
<ContentDefinitions> -->
<ContentDefinition Id="api.phonefactor">
<LoadUri>~/tenant/templates/AzureBlue/multifactor-1.0.0.cshtml</LoadUri>
<RecoveryUri>~/common/default_page_error.html</RecoveryUri>
<DataUri>urn:com:microsoft:aad:b2c:elements:contract:multifactor:1.2.5</DataUri>
<Metadata>
<Item Key="DisplayName">Multi-factor authentication page</Item>
</Metadata>
</ContentDefinition>
<!--
</ContentDefinitions>
</BuildingBlocks> -->
The following technical profiles in used to support MFA.
Technical profile | Type | Description | Changes from the SocialAndLocalAccounts |
---|---|---|---|
PhoneFactor-InputOrVerify | Phone Factor | Provides a user interface to interact with the user to verify, or enroll a phone number. | New |
AAD-UserReadUsingAlternativeSecurityId | AzureActiveDirectory | ||
AAD-UserWriteUsingLogonEmail | AzureActiveDirectory | Persists the phone number to the user profile. | |
AAD-UserReadUsingEmailAddress | AzureActiveDirectory | Returns the phone number to the user profile. | |
AAD-UserWritePasswordUsingObjectId | AzureActiveDirectory | Update user's password | Persists the phone number to the user profile. |
AAD-UserWriteProfileUsingObjectId | AzureActiveDirectory | Update user's profile | Persists the phone number to the user profile. |
AAD-UserReadUsingObjectId | AzureActiveDirectory | Read user profile by user object ID | Returns the phone number to the user profile. |
AAD-UserWritePhoneNumberUsingObjectId | AzureActiveDirectory | Persists the phone number to the user profile. | New |
LocalAccountDiscoveryUsingEmailAddress | SelfAsserted | Password reset flow | Returns the phone number to the user profile. |
LocalAccountWritePasswordUsingObjectId | SelfAsserted | Input claim | |
SM-MFA | SSO | MFA session manager | New |
<!--
<ClaimsProviders> -->
<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>
<ClaimsProvider>
<DisplayName>Azure Active Directory</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="AAD-UserReadUsingAlternativeSecurityId">
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="strongAuthenticationPhoneNumber"/>
</OutputClaims>
</TechnicalProfile>
<TechnicalProfile Id="AAD-UserWriteUsingLogonEmail">
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="Verified.strongAuthenticationPhoneNumber" PartnerClaimType="strongAuthenticationPhoneNumber"/>
</PersistedClaims>
</TechnicalProfile>
<TechnicalProfile Id="AAD-UserReadUsingEmailAddress">
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="strongAuthenticationPhoneNumber"/>
</OutputClaims>
</TechnicalProfile>
<TechnicalProfile Id="AAD-UserWritePasswordUsingObjectId">
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="Verified.strongAuthenticationPhoneNumber" PartnerClaimType="strongAuthenticationPhoneNumber"/>
</PersistedClaims>
</TechnicalProfile>
<TechnicalProfile Id="AAD-UserWriteProfileUsingObjectId">
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="Verified.strongAuthenticationPhoneNumber" PartnerClaimType="strongAuthenticationPhoneNumber"/>
</PersistedClaims>
</TechnicalProfile>
<TechnicalProfile Id="AAD-UserReadUsingObjectId">
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="strongAuthenticationPhoneNumber"/>
</OutputClaims>
</TechnicalProfile>
<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="Verified.strongAuthenticationPhoneNumber" PartnerClaimType="strongAuthenticationPhoneNumber"/>
</PersistedClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common"/>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Local Account</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="LocalAccountDiscoveryUsingEmailAddress">
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="strongAuthenticationPhoneNumber"/>
</OutputClaims>
</TechnicalProfile>
<TechnicalProfile Id="LocalAccountWritePasswordUsingObjectId">
<InputClaims>
<InputClaim ClaimTypeReferenceId="Verified.strongAuthenticationPhoneNumber"/>
</InputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Session Management</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="SM-MFA">
<DisplayName>Session Mananagement Provider</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.SSO.DefaultSSOSessionProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="Verified.strongAuthenticationPhoneNumber"/>
</PersistedClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="isActiveMFASession" DefaultValue="true"/>
</OutputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<!--
</ClaimsProviders> -->
The following are the required orchestration steps required for MFA. The PhoneFactor-Verify registers (if the phone number claim is empty), or verifies (if the phone number is stored in the directory).
<UserJourneys>
<UserJourney Id="SignUpOrSignIn">
<OrchestrationSteps>
...
<OrchestrationStep Order="7" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>isActiveMFASession</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="PhoneFactor-Verify" TechnicalProfileReferenceId="PhoneFactor-InputOrVerify"/>
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="8" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>newPhoneNumberEntered</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserWriteWithObjectId" TechnicalProfileReferenceId="AAD-UserWritePhoneNumberUsingObjectId"/>
</ClaimsExchanges>
</OrchestrationStep>
...
</OrchestrationSteps>
</UserJourney>
<UserJourney Id="ProfileEdit">
<OrchestrationSteps>
...
<OrchestrationStep Order="5" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="PhoneFactor" TechnicalProfileReferenceId="PhoneFactor-InputOrVerify"/>
</ClaimsExchanges>
</OrchestrationStep>
...
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb"/>
</UserJourney>
<UserJourney Id="PasswordReset">
<OrchestrationSteps>
...
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="PhoneFactor-Verify" TechnicalProfileReferenceId="PhoneFactor-InputOrVerify"/>
</ClaimsExchanges>
</OrchestrationStep>
...
<ClientDefinition ReferenceId="DefaultWeb"/>
</UserJourney>
</UserJourneys>