'How do I make the email address (signInName) read only for the sign-in form

I am creating an Azure AD B2C custom policy for inviting users to user my applications. As part of the invite I set custom user attributes.

In my user journey, if the user exists I send them to a sign in screen with the email pre-populated.

How do I make the email address (signInName) field read only so that the user can't apply the invite to a different account?

This is the technical profile I have for signing in. The signInName is populated by my invite token, but it is not read only.

    <TechnicalProfile Id="LocalAccountSigninWithReadOnlyEmail">
      <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="setting.showSignupLink">false</Item>
        <Item Key="SignUpTarget">SignUpWithLogonEmailExchange</Item>
        <Item Key="setting.operatingMode">Email</Item>
        <Item Key="ContentDefinitionReferenceId">api.selfasserted</Item>
      </Metadata>
      <IncludeInSso>false</IncludeInSso>
      <InputClaims>
        <InputClaim ClaimTypeReferenceId="email" Required="true" PartnerClaimType="signInName" />
      </InputClaims>
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="signInName" Required="true" />
        <OutputClaim ClaimTypeReferenceId="password" Required="true" />
        <OutputClaim ClaimTypeReferenceId="objectId" />
        <OutputClaim ClaimTypeReferenceId="authenticationSource" />
      </OutputClaims>
      <OutputClaimsTransformations>
        <OutputClaimsTransformation ReferenceId="CopySignInNameToReadOnly" />
      </OutputClaimsTransformations>
      <ValidationTechnicalProfiles>
        <ValidationTechnicalProfile ReferenceId="login-NonInteractive" />
      </ValidationTechnicalProfiles>
      <UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
    </TechnicalProfile>


Solution 1:[1]

Create a readonly field.

<ClaimType Id="readOnlyEmail">
    <DisplayName>Email Address</DisplayName>
    <DataType>string</DataType>
    <UserHelpText/>
    <UserInputType>Readonly</UserInputType>
</ClaimType>

Copy email via a claims transformation e.g.

<ClaimsTransformation Id="CreateReadonlyEmailClaim" TransformationMethod="FormatStringClaim">
    <InputClaims>
        <InputClaim ClaimTypeReferenceId="mail" TransformationClaimType="inputClaim"/>
    </InputClaims>
    <InputParameters>
        <InputParameter Id="stringFormat" DataType="string" Value="{0}"/>
    </InputParameters>
    <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="readonlyEmail" TransformationClaimType="outputClaim"/>
    </OutputClaims>
</ClaimsTransformation>

Invoke the transformation e.g. <InputClaimsTransformations> and then use the readonly field in the screen.

Solution 2:[2]

I had to face the same problem, I mean read-only email for sign-in custom flow. First of all, you have to create read-only custom claim:

<ClaimType Id="ReadOnlyEmail">
   <DisplayName>Verified Email Address</DisplayName>
   <DataType>string</DataType>
   <UserInputType>Readonly</UserInputType>
</ClaimType>

Next, you have to create two custom claims transformations:

<ClaimsTransformation Id="CopyEmailToReadOnlyEmail" TransformationMethod="FormatStringClaim">
    <InputClaims>
      <InputClaim ClaimTypeReferenceId="email" TransformationClaimType="inputClaim" />
    </InputClaims>
    <InputParameters>
      <InputParameter Id="stringFormat" DataType="string" Value="{0}" />
    </InputParameters>
    <OutputClaims>
      <OutputClaim ClaimTypeReferenceId="ReadOnlyEmail" TransformationClaimType="outputClaim" />
    </OutputClaims>
  </ClaimsTransformation>

  <ClaimsTransformation Id="CopyEmailToSignInName" TransformationMethod="FormatStringClaim">
    <InputClaims>
      <InputClaim ClaimTypeReferenceId="email" TransformationClaimType="inputClaim" />
    </InputClaims>
    <InputParameters>
      <InputParameter Id="stringFormat" DataType="string" Value="{0}" />
    </InputParameters>
    <OutputClaims>
      <OutputClaim ClaimTypeReferenceId="signInName" TransformationClaimType="outputClaim" />
    </OutputClaims>
  </ClaimsTransformation>

And finally it is custom claims provider for sign-in with read-only email:

<ClaimsProvider>
  <DisplayName>Local Account Sign In</DisplayName>
  <TechnicalProfiles>
    <TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Password">
      <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>
        <Item Key="setting.showCancelButton">false</Item>
      </Metadata>
      <IncludeInSso>false</IncludeInSso>
      <InputClaimsTransformations>
        <InputClaimsTransformation ReferenceId="CopyEmailToReadOnlyEmail" />
        <InputClaimsTransformation ReferenceId="CopyEmailToSignInName" />
      </InputClaimsTransformations>
      <InputClaims>
        <InputClaim ClaimTypeReferenceId="signInName" />
        <InputClaim ClaimTypeReferenceId="ReadOnlyEmail" />
      </InputClaims>
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="ReadOnlyEmail" Required="true" />
        <OutputClaim ClaimTypeReferenceId="password" Required="true" />
        <OutputClaim ClaimTypeReferenceId="objectId" />
        <OutputClaim ClaimTypeReferenceId="authenticationSource" />
      </OutputClaims>
      <ValidationTechnicalProfiles>
        <!-- check users password if user existed -->
        <ValidationTechnicalProfile ReferenceId="login-NonInteractive" />          
      </ValidationTechnicalProfiles>
      <UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
    </TechnicalProfile>
  </TechnicalProfiles>
</ClaimsProvider>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 rbrayb
Solution 2 Mateusz Radny