Custom Policy does not respect email operating mode

Viewed 375

When I run my signup_signin custom policy I receive "Sign in with your sign in name" in the UI, however I'd like for this to be an email address sign in. From what I can tell the TrustFrameworkBase has the operatingMode set to email, however I'm not certain why I'm not getting the correct outcome. Do I need to adjust the SelfAsserted-LocalAccountSignin-Email technical profile in some way?

TrustFrameworkBase unmodified from this:

https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack/blob/master/LocalAccounts/TrustFrameworkBase.xml

TrustFrameworkExtensions.xml

<?xml version="1.0" encoding="utf-8" ?>
<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="{Settings:Tenant}" PolicyId="B2C_1A_TrustFrameworkExtensions" PublicPolicyUri="http://{Settings:Tenant}/B2C_1A_TrustFrameworkExtensions">

  <BasePolicy>
    <TenantId>{Settings:Tenant}</TenantId>
    <PolicyId>B2C_1A_TrustFrameworkBase</PolicyId>
  </BasePolicy>
  <BuildingBlocks>
    <ClaimsSchema>
      <!-- Custom Attributes -->
      <!-- Seamless Migration -->
      <ClaimType Id="extension_RequiresMigration">
        <DisplayName>extension_RequiresMigration</DisplayName>
        <DataType>boolean</DataType>
        <AdminHelpText>Migration status for the user's account. If true, reach out to legacy IdP to migrate user.</AdminHelpText>
      </ClaimType>

      <ClaimType Id="TokenSuccess">
        <DisplayName>TokenSuccess</DisplayName>
        <DataType>boolean</DataType>
        <AdminHelpText>Add help text here</AdminHelpText>
      </ClaimType>

      <ClaimType Id="MigrationRequired">
        <DisplayName>MigrationRequired</DisplayName>
        <DataType>boolean</DataType>
        <AdminHelpText>Holds the value of false when the legacy IdP authentication succeeded</AdminHelpText>
      </ClaimType>

    </ClaimsSchema>

    <ContentDefinitions>

      <!-- This content definition is to render an error page that displays unhandled errors. -->
      <ContentDefinition Id="api.error">
        <LoadUri>~/tenant/templates/AzureBlue/exception.cshtml</LoadUri>
        <RecoveryUri>~/common/default_page_error.html</RecoveryUri>
        <DataUri>urn:com:microsoft:aad:b2c:elements:contract:globalexception:1.2.0</DataUri>
        <Metadata>
          <Item Key="DisplayName">Error page</Item>
        </Metadata>
      </ContentDefinition>

      <ContentDefinition Id="api.idpselections">
        <LoadUri>~/tenant/templates/AzureBlue/idpSelector.cshtml</LoadUri>
        <RecoveryUri>~/common/default_page_error.html</RecoveryUri>
        <DataUri>urn:com:microsoft:aad:b2c:elements:contract:providerselection:1.2.0</DataUri>
        <Metadata>
          <Item Key="DisplayName">Idp selection page</Item>
          <Item Key="language.intro">Sign in</Item>
        </Metadata>
      </ContentDefinition>

      <ContentDefinition Id="api.idpselections.signup">
        <LoadUri>~/tenant/templates/AzureBlue/idpSelector.cshtml</LoadUri>
        <RecoveryUri>~/common/default_page_error.html</RecoveryUri>
        <DataUri>urn:com:microsoft:aad:b2c:elements:contract:providerselection:1.2.0</DataUri>
        <Metadata>
          <Item Key="DisplayName">Idp selection page</Item>
          <Item Key="language.intro">Sign up</Item>
        </Metadata>
      </ContentDefinition>

      <ContentDefinition Id="api.signuporsignin">
        <LoadUri>https://{Settings:BlobStorageAccount}.blob.core.windows.net/{Settings:BlobContainer}/html/sign_in.html</LoadUri>
        <RecoveryUri>~/common/default_page_error.html</RecoveryUri>
        <DataUri>urn:com:microsoft:aad:b2c:elements:contract:unifiedssp:2.1.0</DataUri>
        <Metadata>
          <Item Key="DisplayName">Signin and Signup</Item>
        </Metadata>
      </ContentDefinition>

      <ContentDefinition Id="api.selfasserted">
        <LoadUri>~/tenant/templates/AzureBlue/selfAsserted.cshtml</LoadUri>
        <RecoveryUri>~/common/default_page_error.html</RecoveryUri>
        <DataUri>urn:com:microsoft:aad:b2c:elements:contract:selfasserted:2.1.0</DataUri>
        <Metadata>
          <Item Key="DisplayName">Collect information from user page</Item>
        </Metadata>
      </ContentDefinition>

      <ContentDefinition Id="api.selfasserted.profileupdate">
        <LoadUri>~/tenant/templates/AzureBlue/selfAsserted.cshtml</LoadUri>
        <RecoveryUri>~/common/default_page_error.html</RecoveryUri>
        <DataUri>urn:com:microsoft:aad:b2c:elements:contract:selfasserted:2.1.0</DataUri>
        <Metadata>
          <Item Key="DisplayName">Collect information from user page</Item>
        </Metadata>
      </ContentDefinition>

      <ContentDefinition Id="api.localaccountsignup">
        <LoadUri>~/tenant/templates/AzureBlue/selfAsserted.cshtml</LoadUri>
        <RecoveryUri>~/common/default_page_error.html</RecoveryUri>
        <DataUri>urn:com:microsoft:aad:b2c:elements:contract:selfasserted:2.1.0</DataUri>
        <Metadata>
          <Item Key="DisplayName">Local account sign up page</Item>
        </Metadata>
      </ContentDefinition>

      <ContentDefinition Id="api.localaccountpasswordreset">
        <LoadUri>~/tenant/templates/AzureBlue/selfAsserted.cshtml</LoadUri>
        <RecoveryUri>~/common/default_page_error.html</RecoveryUri>
        <DataUri>urn:com:microsoft:aad:b2c:elements:contract:selfasserted:2.1.0</DataUri>
        <Metadata>
          <Item Key="DisplayName">Local account change password page</Item>
        </Metadata>
      </ContentDefinition>

    </ContentDefinitions>
  </BuildingBlocks>

  <ClaimsProviders>
    <!-- Allows for referencing custom attributes in custom policies -->
    <!-- https://docs.microsoft.com/en-us/azure/active-directory-b2c/custom-policy-custom-attributes#modify-your-custom-policy -->
    <ClaimsProvider>
      <DisplayName>Azure Active Directory</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="AAD-Common">
          <Metadata>
            <Item Key="ClientId">{Settings:B2CExtensionsAppId}</Item>
            <Item Key="ApplicationObjectId">{Settings:B2CExtensionsObjectId}</Item>
          </Metadata>
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>

    <!-- Seamless Migration -->
    <ClaimsProvider>
      <DisplayName>Local Account SignIn - Read Migration Status</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="Get-RequiresMigration-Status-SignIn">
          <Metadata>
            <Item Key="Operation">Read</Item>
            <Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
            <Item Key="UserMessageIfClaimsPrincipalDoesNotExist">An account could not be found for the provided user ID.</Item>
          </Metadata>
          <IncludeInSso>false</IncludeInSso>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="signInNames.emailAddress" Required="true"/>
          </InputClaims>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="objectId"/>
            <OutputClaim ClaimTypeReferenceId="extension_RequiresMigration" DefaultValue="false"/>
          </OutputClaims>
          <IncludeTechnicalProfile ReferenceId="AAD-Common"/>
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>

    <ClaimsProvider>
      <DisplayName>REST API to communicate with Legacy IdP</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="UserMigrationViaLegacyIdP">
          <DisplayName>REST API call to communicate with Legacy IdP</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://{Settings:TIAPIBaseUrl}/SSOAuth</Item>
            <Item Key="AuthenticationType">None</Item>
            <Item Key="SendClaimsIn">Body</Item>
          </Metadata>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="email"/>
            <InputClaim ClaimTypeReferenceId="password"/>
          </InputClaims>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="TokenSuccess" DefaultValue="false"/>
            <OutputClaim ClaimTypeReferenceId="MigrationRequired"/>
          </OutputClaims>
          <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop"/>
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>

    <ClaimsProvider>
      <DisplayName>Local Account SignIn - Write new password and unmark for migration</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="AAD-WritePasswordAndFlipMigratedFlag">
          <Metadata>
            <Item Key="Operation">Write</Item>
            <Item Key="RaiseErrorIfClaimsPrincipalAlreadyExists">false</Item>
          </Metadata>
          <IncludeInSso>false</IncludeInSso>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="objectId" Required="true"/>
          </InputClaims>
          <PersistedClaims>
            <PersistedClaim ClaimTypeReferenceId="objectId"/>
            <PersistedClaim ClaimTypeReferenceId="userPrincipalName" />
            <PersistedClaim ClaimTypeReferenceId="displayName" />
            <PersistedClaim ClaimTypeReferenceId="password" PartnerClaimType="password"/>
            <PersistedClaim ClaimTypeReferenceId="passwordPolicies" DefaultValue="DisablePasswordExpiration, DisableStrongPassword" AlwaysUseDefaultValue="true"/>
            <PersistedClaim ClaimTypeReferenceId="MigrationRequired" PartnerClaimType="extension_RequiresMigration"/>
          </PersistedClaims>
          <IncludeTechnicalProfile ReferenceId="AAD-Common"/>
          <UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD"/>
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>
    <!-- End Seamless Migration -->

    <ClaimsProvider>
      <DisplayName>Local Account SignIn</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="login-NonInteractive">
          <Metadata>
            <Item Key="client_id">{Settings:ProxyIdentityExperienceFrameworkAppId}</Item>
            <Item Key="IdTokenAudience">{Settings:IdentityExperienceFrameworkAppId}</Item>
          </Metadata>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="client_id" DefaultValue="{Settings:ProxyIdentityExperienceFrameworkAppId}" />
            <InputClaim ClaimTypeReferenceId="resource_id" PartnerClaimType="resource" DefaultValue="{Settings:IdentityExperienceFrameworkAppId}" />
          </InputClaims>
        </TechnicalProfile>

        <!-- Seamless Migration -->
        <TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Email">
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="extension_RequiresMigration"/>
          </OutputClaims>
          <ValidationTechnicalProfiles>
            <ValidationTechnicalProfile ReferenceId="Get-RequiresMigration-Status-SignIn" ContinueOnError="false"/>
            <ValidationTechnicalProfile ReferenceId="UserMigrationViaLegacyIdP" ContinueOnError="false">
              <Preconditions>
                <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
                  <Value>extension_RequiresMigration</Value>
                  <Value>False</Value>
                  <Action>SkipThisValidationTechnicalProfile</Action>
                </Precondition>
              </Preconditions>
            </ValidationTechnicalProfile>

            <ValidationTechnicalProfile ReferenceId="AAD-WritePasswordAndFlipMigratedFlag" ContinueOnError="false">
              <Preconditions>
                <Precondition Type="ClaimsExist" ExecuteActionsIf="false">
                  <Value>TokenSuccess</Value>
                  <Action>SkipThisValidationTechnicalProfile</Action>
                </Precondition>
              </Preconditions>
            </ValidationTechnicalProfile>

            <ValidationTechnicalProfile ReferenceId="login-NonInteractive"/>
          </ValidationTechnicalProfiles>
        </TechnicalProfile>
        <!-- End Seamless Migration-->
      </TechnicalProfiles>
    </ClaimsProvider>
  </ClaimsProviders>

  <!--UserJourneys>
    
    </UserJourneys-->

</TrustFrameworkPolicy>

SignUpOrSignin.xml

<?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="{Settings:Tenant}" PolicyId="B2C_1A_SignUp_Signin" PublicPolicyUri="http://{Settings:Tenant}/B2C_1A_SignUp_Signin" UserJourneyRecorderEndpoint="urn:journeyrecorder:applicationinsights" DeploymentMode="{Settings:ApplicationInsightsDeploymentMode}">

    <BasePolicy>
        <TenantId>{Settings:Tenant}</TenantId>
        <PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId>
    </BasePolicy>

    <RelyingParty>
        <DefaultUserJourney ReferenceId="SignUpOrSignIn" />
        <UserJourneyBehaviors>
            <JourneyInsights TelemetryEngine="ApplicationInsights" InstrumentationKey="{Settings:ApplicationInsightsInstrumentationKey}" DeveloperMode="{Settings:ApplicationInsightsDeveloperMode}" ClientEnabled="false" ServerEnabled="true" TelemetryVersion="1.0.0" />
            <ScriptExecution>Allow</ScriptExecution>
        </UserJourneyBehaviors>
        <TechnicalProfile Id="PolicyProfile">
            <DisplayName>PolicyProfile</DisplayName>
            <Protocol Name="OpenIdConnect" />
            <InputClaims>
                <InputClaim ClaimTypeReferenceId="passwordPolicies" DefaultValue="DisablePasswordExpiration, DisableStrongPassword" />
            </InputClaims>
            <OutputClaims>
                <OutputClaim ClaimTypeReferenceId="displayName" />
                <OutputClaim ClaimTypeReferenceId="givenName" />
                <OutputClaim ClaimTypeReferenceId="surname" />
                <OutputClaim ClaimTypeReferenceId="email" />
                <OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub"/>
                <OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />

            </OutputClaims>
            <SubjectNamingInfo ClaimType="sub" />
        </TechnicalProfile>
    </RelyingParty>
</TrustFrameworkPolicy>

enter image description here

2 Answers

This seems to be an issue with Page Layout Version 2.1.x as indicated by JasSuri. My current workaround working solution:

<LocalizedString ElementType="UxElement"
  StringId="local_intro_generic">Sign in with your email address</LocalizedString>

<LocalizedString ElementType="ClaimType" 
  ElementId="signInName" 
  StringId="DisplayName">Email Address</LocalizedString>

Its not perfect, but fine for the moment for me. The main problem is that the field is no longer of type email, which causes some issues (e.g. mobile keyboards, password managers)

edit: Adding the code by JasSuri in order to make this one "complete" answer:

To fix email field type, use <UserInputType>EmailBox</UserInputType>.

   <ClaimsSchema>
     <ClaimType Id="signInName">
       <DisplayName>Email Address</DisplayName>
       <DataType>string</DataType>
       <AdminHelpText>Email address that the user can use to sign in.</AdminHelpText>
       <UserHelpText>Email address to use for signing in.</UserHelpText>
       <UserInputType>EmailBox</UserInputType>
     </ClaimType>
   </ClaimsSchema>

To fix email field type, use <UserInputType>EmailBox</UserInputType>.

    <ClaimsSchema>
      <ClaimType Id="signInName">
        <DisplayName>Email Address</DisplayName>
        <DataType>string</DataType>
        <AdminHelpText>Email address that the user can use to sign in.</AdminHelpText>
        <UserHelpText>Email address to use for signing in.</UserHelpText>
        <UserInputType>EmailBox</UserInputType>
      </ClaimType>
    </ClaimsSchema>

We will try make this default in the starter pack.

https://docs.microsoft.com/en-us/azure/active-directory-b2c/claimsschema#userinputtype

Related