We have implemented a B2C custom policy that allows users to sign in with B2C Local or Social (Entra) accounts. Our login screen has a unified sign-in page that was modeled on the SocialAndLocalAccounts starter pack, so we have the option to sign in with an Entra account, or username/password for local accounts.
<OrchestrationStep Order="2" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
<ClaimsProviderSelection TargetClaimsExchangeId="AzureADCommonExchange" />
<ClaimsProviderSelection TargetClaimsExchangeId="ForgotPasswordExchange" />
</ClaimsProviderSelections>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
</ClaimsExchanges>
</OrchestrationStep>
We use an invitation and magic link for sign-up, so this is not available during sign-in.
This all works well, but our users sometimes try to put their Entra credentials in the local account email/password, which results in an error.
I have tried to implement a self-asserted TP to capture the sign-in email which would then determine which IDP to use for login. Similar to the ‘Home-Realm-Discovery-Modern’ B2C sample pack
<ClaimsProvider>
<DisplayName>Local Account</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="SelfAsserted-Signin-Email">
<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="ContentDefinitionReferenceId">api.selfasserted</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="signInName" Required="true" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
My idea was to use AAD-UserReadUsingEmailAddress, but this only picks up local accounts and I cannot get it to return information about Social accounts – it returns a ‘Not Found’ error. The RaiseErrorIfClaimsPrincipalDoesNotExist option is ‘false’.
<TechnicalProfile Id="AAD-UserReadUsingEmailAddress">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="signInNames.emailAddress" Required="true" />
</InputClaims>
...
</TechnicalProfile>
This approach could possibly work if I could suppress the error for Entra accounts and continue the journey. Even if I did get this working, then all users would need to enter an email address, even for Social logins which affects their user experience.
Ideally, I’d like to keep the unified login page and check the sign-in email before it is sent to the SelfAsserted-LocalAccountSignin-Email TP, and route it to the AADCommon-OpenIdConnect TP if it is actually an email associated with a Social Account.
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
...
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
</ClaimsExchanges>
Is there some way I could do this?
The reason it doesn’t work is that local accounts are referenced by objectId (“AAD-UserReadUsingObjectId”), and social accounts are referenced by alternativeSecurityId (“AAD-UserReadUsingAlternativeSecurityId”).
3