Azure B2C - The provided token does not contain a valid issuer - azure-ad-b2c

I'm using Azure B2C to connect to an external OpenID Connect identity provider, i created a basic user flow within B2C which works but only brings back a small number of claims so i need to create a custom policy to pass custom input parameters to my IDP and collect additional claims.
I started with the SocialAndLocalAccount sample and modified with my IDP's details:
TrustFrameworkBase.xml (Cut down to Technical Profile + User Journey)
...
<ClaimType Id="sub">
<DisplayName>Subject</DisplayName>
<DataType>string</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OpenIdConnect" PartnerClaimType="sub" />
</DefaultPartnerClaimTypes>
<UserHelpText />
</ClaimType>
<ClaimType Id="ui_locales">
<DisplayName>UI Locales</DisplayName>
<DataType>string</DataType>
<UserHelpText>Special parameter passed for account authentication to Tell Us Once.</UserHelpText>
</ClaimType>
<ClaimType Id="givenName">
<DisplayName>Given Name</DisplayName>
<DataType>string</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OAuth2" PartnerClaimType="given_name" />
<Protocol Name="OpenIdConnect" PartnerClaimType="given_name" />
<Protocol Name="SAML2" PartnerClaimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname" />
</DefaultPartnerClaimTypes>
<UserHelpText>Your given name (also known as first name).</UserHelpText>
<UserInputType>TextBox</UserInputType>
</ClaimType>
<ClaimType Id="surname">
<DisplayName>Surname</DisplayName>
<DataType>string</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OAuth2" PartnerClaimType="family_name" />
<Protocol Name="OpenIdConnect" PartnerClaimType="family_name" />
<Protocol Name="SAML2" PartnerClaimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname" />
</DefaultPartnerClaimTypes>
<UserHelpText>Your surname (also known as family name or last name).</UserHelpText>
<UserInputType>TextBox</UserInputType>
</ClaimType>
<ClaimType Id="DateOfBirth">
<DisplayName>Date Of Birth</DisplayName>
<DataType>string</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OAuth2" PartnerClaimType="birthdate" />
<Protocol Name="OpenIdConnect" PartnerClaimType="birthdate" />
<Protocol Name="SAML2" PartnerClaimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/dateofbirth" />
</DefaultPartnerClaimTypes>
<UserHelpText>Your date of birth.</UserHelpText>
<UserInputType>TextBox</UserInputType>
</ClaimType>
...
<ClaimsProvider>
<Domain>tellusonce</Domain>
<DisplayName>Tell Us Once</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="TUO-OpenIdConnect">
<DisplayName>Tell Us Once Login</DisplayName>
<Description>Login through Tell Us Once</Description>
<Protocol Name="OpenIdConnect" />
<Metadata>
<Item Key="client_id">this is where i put my client id</Item>
<Item Key="ProviderName">Tell Us Once</Item>
<Item Key="METADATA">this is where my metadata is</Item>
<Item Key="response_types">code</Item>
<Item Key="response_mode">form_post</Item>
<Item Key="scope">openid</Item>
<Item Key="HttpBinding">POST</Item>
<Item Key="UsePolicyInRedirectUri">false</Item>
</Metadata>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_TellUsOnceSecret" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="openid profile" />
<InputClaim ClaimTypeReferenceId="prompt" DefaultValue="login" />
<InputClaim ClaimTypeReferenceId="ui_locales" DefaultValue="en-US" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="issuerUserId" PartnerClaimType="sub" />
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />
<OutputClaim ClaimTypeReferenceId="middleName" DefaultValue="" />
<OutputClaim ClaimTypeReferenceId="surname" PartnerClaimType="family_name" />
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
<OutputClaim ClaimTypeReferenceId="DateOfBirth" PartnerClaimType="birthdate" />
<OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="ITP-B2C" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="ITP-Auth-DEV-OIDC" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName" />
<OutputClaimsTransformation ReferenceId="CreateUserPrincipalName" />
<OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId" />
<OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromAlternativeSecurityId" />
</OutputClaimsTransformations>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
...
<UserJourneys>
<UserJourney Id="SignUpOrSignInOidc">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="TUO-OIDCExchange" />
</ClaimsProviderSelections>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="TUO-OIDCExchange" TechnicalProfileReferenceId="TUO-OpenIdConnect" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- For social IDP authentication, attempt to find the user account in the directory. -->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadUsingAlternativeSecurityId" TechnicalProfileReferenceId="AAD-UserReadUsingAlternativeSecurityId-NoError" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Create the user in the directory if one does not already exist. -->
<OrchestrationStep Order="4" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserWrite" TechnicalProfileReferenceId="AAD-UserWriteUsingAlternativeSecurityId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="5" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
</UserJourneys>
TrustFrameworkExtensions.xml
<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="itpauthdev.onmicrosoft.com"
PolicyId="B2C_1A_TrustFrameworkExtensions"
PublicPolicyUri="http://itpauthdev.onmicrosoft.com/B2C_1A_TrustFrameworkExtensions"
TenantObjectId="ae2201eb-e4e9-44e7-8c73-b52e37ba01f8">
<BasePolicy>
<TenantId>itpauthdev.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkBase</PolicyId>
</BasePolicy>
<BuildingBlocks></BuildingBlocks>
<ClaimsProviders>
<ClaimsProvider>
<Domain>tellusonce</Domain>
<DisplayName>Tell Us Once</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="TUO-OpenIdConnect">
<DisplayName>Tell Us Once Login</DisplayName>
<Description>Login through Tell Us Once</Description>
<Protocol Name="OpenIdConnect" />
<Metadata>
<Item Key="client_id">this is where i put my client id</Item>
<Item Key="ProviderName">Tell Us Once</Item>
<Item Key="METADATA">this is where my metadata is</Item>
<Item Key="response_types">code</Item>
<Item Key="response_mode">form_post</Item>
<Item Key="scope">openid</Item>
<Item Key="HttpBinding">POST</Item>
<Item Key="UsePolicyInRedirectUri">false</Item>
</Metadata>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_TellUsOnceSecret" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="acr_values" DefaultValue="urn:id.gov.au:tdif:acr:ip1:cl1" />
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="openid profile" />
<InputClaim ClaimTypeReferenceId="prompt" DefaultValue="login" />
<InputClaim ClaimTypeReferenceId="ui_locales" DefaultValue="en-US" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="issuerUserId" PartnerClaimType="sub" />
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />
<OutputClaim ClaimTypeReferenceId="middleName" DefaultValue="" />
<OutputClaim ClaimTypeReferenceId="surname" PartnerClaimType="family_name" />
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
<OutputClaim ClaimTypeReferenceId="DateOfBirth" PartnerClaimType="birthdate" />
<OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="ITP-B2C" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="ITP-Auth-DEV-OIDC" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName" />
<OutputClaimsTransformation ReferenceId="CreateUserPrincipalName" />
<OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId" />
<OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromAlternativeSecurityId" />
</OutputClaimsTransformations>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<UserJourneys>
<UserJourney Id="SignUpOrSignInOidc">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="TUO-OIDCExchange" />
</ClaimsProviderSelections>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="TUO-OIDCExchange" TechnicalProfileReferenceId="TUO-OpenIdConnect" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- For social IDP authentication, attempt to find the user account in the directory. -->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadUsingAlternativeSecurityId" TechnicalProfileReferenceId="AAD-UserReadUsingAlternativeSecurityId-NoError" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Create the user in the directory if one does not already exist. -->
<OrchestrationStep Order="4" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserWrite" TechnicalProfileReferenceId="AAD-UserWriteUsingAlternativeSecurityId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="5" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
</UserJourneys>
</TrustFrameworkPolicy>
signin_signup_oidc.xml
<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="itpauthdev.onmicrosoft.com"
PolicyId="B2C_1A_signup_signin_oidc"
PublicPolicyUri="http://itpauthdev.onmicrosoft.com/B2C_1A_signup_signin"
TenantObjectId="ae2201eb-e4e9-44e7-8c73-b52e37ba01f8">
<BasePolicy>
<TenantId>itpauthdev.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId>
</BasePolicy>
<RelyingParty>
<DefaultUserJourney ReferenceId="SignUpOrSignInOidc" />
<UserJourneyBehaviors></UserJourneyBehaviors>
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="middleName" DefaultValue="" />
<OutputClaim ClaimTypeReferenceId="surname" />
<OutputClaim ClaimTypeReferenceId="email" />
<OutputClaim ClaimTypeReferenceId="DateOfBirth" DefaultValue="ClaimNotFound" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" />
<OutputClaim ClaimTypeReferenceId="identityProvider" />
<OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
</TrustFrameworkPolicy>
When i click "B2C_1A_signin_signup_oidc" and choose Run User Flow i get my IDP's login screen, once i've successfully logged in i get an authorization code response from my IDP, my IDP has been configured with the "/authresp" redirect uri as per Microsoft documentation ("Redirect Uri" section at the bottom) but then it doesn't seem like "/authresp" is exchanging the code for an id_token because when it redirects to jwt.ms i get "token does not contain a valid issuer".
Fiddler Code Response
By using fiddler's inspector i can see that the IDP's endpoint is responding with a code that looks something like this (not actual code):
<HTML>
<HEAD>
<TITLE>OIDC Form_Post Response</TITLE>
</HEAD>
<BODY Onload="document.forms[0].submit()">
<FORM METHOD="POST" ACTION="https://itpauthdev.b2clogin.com/itpauthdev.onmicrosoft.com/oauth2/authresp"> <INPUT
TYPE="HIDDEN" NAME="code"
VALUE="4cfed1f1-bfcc-42db-b39c-d79480f6d333.056c6e05-eaec-426a-a5e7-bf9f66f962be.15ac212d-38b7-4e9b-80e2-a948be9360e5" />
<INPUT TYPE="HIDDEN" NAME="state"
VALUE="StateProperties=eyJTSUQiOiJ4LW1zLWNwaW0tcmM6M2RkYmRhYjktY2VkZS00MDA4LTliNWYtOGRmZjU2ZDZmZDYzIiveWIlEIjoiMmFtkoc5YjUtMzg0Zi00M2JmLThkMDUtMjIzMGYxNzU1M2JjIiwiVE9CRJI6ImFlMjIwMWViLWU0ZTktNDRlNy04YzczLWI1MmUzN2JhMDFmOCJ9" />
<INPUT TYPE="HIDDEN" NAME="session_state" VALUE="65e01676-90d1-4c5e-a7f4-66dfd7b3211b" /> <NOSCRIPT>
<P>JavaScript is disabled. We strongly recommend to enable it. Click the button below to continue .</P>
<INPUT name="continue" TYPE="SUBMIT" VALUE="CONTINUE" />
</NOSCRIPT> </FORM>
</BODY>
</HTML>
Fiddler AuthResp Response
Using Fiddler once again, i can see that AuthResp does not exchange the code for an id_token using the IDP's token endpoint (or encounters an error, i don't get told anything helpful). Instead i get back:
<html>
<head>
<title>Object moved</title>
</head>
<body>
<h2>Object moved to here.
</h2>
</body>
</html>
Questions
I read in documentation somewhere that an "OpenIDConnect" technical profile will automatically exchange the code for an id token but i can't seem to find the document, is my configuration missing a step or do i have to do this manually with an extra userjourney?
If there is miscommunication between "/authresp" and the token endpoint, is there somewhere i can look to get a more descriptive error message? Both jwt.ms and the audit logs just say "Token does not contain a valid issuer".

The error means the id token from the OIDC provider has an issuer (iss) claim that doesn’t match the issuer in the well known OIDC config endpoint. You can add an issuer element to the metadata of TUO-OpenIdConnect technical profile to override it with the value that appears in the token.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/openid-connect-technical-profile#metadata
issuer : The unique identifier of an OpenID Connect identity provider. The value of issuer metadata takes precedence over the issuer specified in the OpenID well-known configuration endpoint. If specified, Azure AD B2C checks whether the iss claim in a token returned by the identity provider is equal to the one specified in the issuer metadata.

I think the technical profile for sign in is not configured correctly. You can find a working example using OpenIdConnect in the starter pack samples.
Please refer in the samples e.g. to
LocalAccounts\TrustFrameworkBase.xml: Technical profile login-NonInteractive
LocalAccounts\TrustFrameworkExtensions.xml: Technical profile login-NonInteractive
LocalAccounts\SignUpOrSigning.xml

I was using ADB2C and added AAD as identity provider for Home realm discovery. HRD was working as expected and it was redirecting user to AAD(https://login.microsoftonline.com) basis on domain for authentication and after successful authentication it was redirecting to jwt.ms with "AADB2C90238: The provided token does not contain a valid issuer. Please provide another token and try again." error code.
I was able to resolve this by removing below item from Metadata tag.
<Item Key="ProviderName">https://sts.windows.net/ffc9d30c-a7e7-486d-bbcd-66d88af6f4c9/</Item>
Click here for more information

Related

B2C Custom Impersonation Policy Giving Error

I am trying to create a custom policy that allows a user to impersonate another user. It is based off the impersonation sample (https://github.com/azure-ad-b2c/samples/tree/master/policies/impersonation) and uses the starter pack.
However I didn't want social accounts so I used the LocalAccounts starter pack and modified the policy. It brings up the login screen but then does not bring up the second screen and instead returns a generic error (ServerError: AADB2C90037: An error occurred while processing the request. Please contact administrator of the site you are trying to access.)
Can anyone help me get this policy working or point out the problem in it?
<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="mytenantdev.onmicrosoft.com" PolicyId="B2C_1A_Impersonation" PublicPolicyUri="http://mytenantdev.onmicrosoft.com/B2C_1A_Impersonation" TenantObjectId="b21d8c41-8fdb-4f82-835d-2c1765fcc855">
<BasePolicy>
<TenantId>mytenantdev.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId>
</BasePolicy>
<BuildingBlocks>
<ClaimsSchema>
<!-- Sample: targetEmail is the email of the user originally requested impersonatedUser is the
email value returned from SignInNames.Email from Azure AD after we requested (this is directory data) -->
<ClaimType Id="targetEmail">
<DisplayName>Sign-in on behalf of</DisplayName>
<DataType>string</DataType>
<UserHelpText>Email address of the impersonated user</UserHelpText>
<UserInputType>EmailBox</UserInputType>
<Restriction>
<Pattern RegularExpression="^[a-zA-Z0-9.!#$%&'^_`{}~-]+#[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$" HelpText="Please enter a valid email address." />
</Restriction>
</ClaimType>
<ClaimType Id="impersonatedUser">
<DisplayName>Impersonated account</DisplayName>
<DataType>string</DataType>
<UserHelpText />
</ClaimType>
<!--Sample: Indicates whether a user can impersonate (if the value is 1)-->
<ClaimType Id="extension_can_impersonate">
<DisplayName>Can impersonate</DisplayName>
<DataType>string</DataType>
<UserHelpText>Add help text here</UserHelpText>
</ClaimType>
<!--Sample: Stores the error message if user can't impersonate-->
<ClaimType Id="errorMessage">
<DisplayName>Error</DisplayName>
<DataType>string</DataType>
<UserHelpText>Add help text here</UserHelpText>
<UserInputType>Paragraph</UserInputType>
</ClaimType>
</ClaimsSchema>
<ClaimsTransformations>
<!--Sample: Initiates the errorMessage claims type with the error message-->
<ClaimsTransformation Id="CreateUnsolicitedErrorMessage" TransformationMethod="CreateStringClaim">
<InputParameters>
<InputParameter Id="value" DataType="string" Value="You are not allow to impersonate. Please content your administrator" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="errorMessage" TransformationClaimType="createdClaim" />
</OutputClaims>
</ClaimsTransformation>
</ClaimsTransformations>
</BuildingBlocks>
<ClaimsProviders>
<!--Sample: this technical profile provides input for targetEmail and runs validation technical
profile for to read the impersonated user profile -->
<ClaimsProvider>
<DisplayName>Self Asserted Targeted Email Exchange</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="SelfAsserted-TargetEmailExchange">
<DisplayName>Target Email Page</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>
<!--Sample: indicating that claim resolving should be performed. So, we can read the value of {OAUTH-KV:targetEmail} claim resolver -->
<Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>
</Metadata>
<InputClaims>
<!--Sample: read the targetEmail query string parameter e.g. &targetemail=bob#contoso.com -->
<InputClaim ClaimTypeReferenceId="targetEmail" DefaultValue="{OAUTH-KV:targetEmail}" AlwaysUseDefaultValue="true" />
</InputClaims>
<OutputClaims>
<!-- Required claims -->
<OutputClaim ClaimTypeReferenceId="executed-SelfAsserted-Input" DefaultValue="true" />
<OutputClaim ClaimTypeReferenceId="impersonatedUser" Required="true" />
<OutputClaim ClaimTypeReferenceId="targetEmail" Required="true" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-ImpersonatedUserRead" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
<!-- Sample: Show error message if user is not allowed to impersonate-->
<TechnicalProfile Id="SelfAsserted-ErrorMessage">
<DisplayName>Error message</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>
<!-- Sample: Remove the continue button-->
<Item Key="setting.showContinueButton">false</Item>
</Metadata>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CreateUnsolicitedErrorMessage" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="errorMessage" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="errorMessage" />
</OutputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Azure Active Directory</DisplayName>
<TechnicalProfiles>
<!--Sample: read the extension_can_impersonate attribute-->
<TechnicalProfile Id="AAD-UserReadUsingObjectId">
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="extension_can_impersonate" />
</OutputClaims>
</TechnicalProfile>
<!--Sample: Read target user and return error it doesn't exist.
If exists, pipes value into impersonatedUser claim type -->
<TechnicalProfile Id="AAD-ImpersonatedUserRead">
<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>
<!--Sample: Look up in the signInNames to see if the value in targetEmail contains - look it up-->
<InputClaim ClaimTypeReferenceId="targetEmail" PartnerClaimType="signInNames" Required="true" />
</InputClaims>
<OutputClaims>
<!-- Sample: Returns the value in targetEmail and check in signInNames collection, then
returns value in SignInName and pipe into impersonatedUser-->
<OutputClaim ClaimTypeReferenceId="impersonatedUser" PartnerClaimType="signInNames.emailAddress" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<UserJourneys>
<UserJourney Id="Impersonation">
<OrchestrationSteps>
<!--Sample: Sign-in with your own credentials or select to sign-in with social account -->
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
</ClaimsProviderSelections>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
</ClaimsExchanges>
</OrchestrationStep>
<!--Sample: Sign-in with social account, or create new local account -->
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail" />
</ClaimsExchanges>
</OrchestrationStep>
<!--Sample: this step reads your own account properties-->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Sample: Check whether user can impersonate -->
<OrchestrationStep Order="4" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>extension_can_impersonate</Value>
<Value>1</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SelfAsserted-ErrorMessage" TechnicalProfileReferenceId="SelfAsserted-ErrorMessage" />
</ClaimsExchanges>
</OrchestrationStep>
<!--Sample: Collect the email address of the end user of act of behalf of-->
<OrchestrationStep Order="5" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="TargetEmailExchange" TechnicalProfileReferenceId="SelfAsserted-TargetEmailExchange" />
</ClaimsExchanges>
</OrchestrationStep>
<!--Sample: Issu an access token-->
<OrchestrationStep Order="6" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
</UserJourneys>
<RelyingParty>
<DefaultUserJourney ReferenceId="Impersonation" />
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surname" />
<OutputClaim ClaimTypeReferenceId="email" />
<!-- <OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub"/> -->
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="oid" />
<OutputClaim ClaimTypeReferenceId="impersonatedUser" />
</OutputClaims>
<!-- <SubjectNamingInfo ClaimType="sub" /> -->
<SubjectNamingInfo ClaimType="oid" />
</TechnicalProfile>
</RelyingParty>
</TrustFrameworkPolicy>

Issue token for application access from ADB2C

Application 1 creates a url with id_token that contains all the necessary info to validate a claim (as of now simply validate the user with email & signed claim).
a. Create a self-signed certificate (New-SelfSignedCertificate )
b. For the same, used tokenbuilder to create a URL as :
https://tenant.b2clogin.com/tenant.onmicrosoft.com/oauth2/v2.0/authorize?p=B2C_1A_SIGNIN_WITH_EMAIL&client_id=9994db7f-2dc7-4bac-bf8a-93bf04067890&redirect_uri=https://myredirecturl.com/&nonce=80c307115..&scope=openid&response_type=id_token&id_token_hint=eyJhbGciOiJSUzI1NiIsImtpZCI6IlZWWWwwbUE0YlFYOXg0LVZVTGpfb0VUWHd0VSIsIng1dCI6I......nNpb25fYWdlbmN5
c. Hosted the application with self-signed certificate (Example :
https://b2cidtokenbuilder.azurewebsites.net/)
d. In policy blade created a token with the self-signed certificate (following steps in here)
e. In TrustFrameworkBase updated issuer_secret for JwtIssuer Technical Profile
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_AABIDToken" />
<Key Id="issuer_refresh_token_key" StorageReferenceId="B2C_1A_TokenEncryptionKeyContainer" />
</CryptographicKeys>
f. In B2C_1A_SIGNIN_WITH_EMAIL policy added the
<Metadata
<Item Key="METADATA">https://tenant.b2clogin.com/tenant.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1A_SIGNUP_SIGNIN</Item>
<Item Key="issuer">https://b2cidtokenbuilder.azurewebsites.net/</Item>
</Metadata>
g. Once I click the AD B2C url I am expecting that the policy B2C_1A_SIGNIN_WITH_EMAIL will allow me to login to the application with an access_token.
(Based on info here I should be able to login to my application once the accesstoken has been created by my policy.
But in my current application what I see is not access_token is getting created but an id_token is created with all the info and since there is no access-token my application is asking for AD B2C login (prompt).
https://myapplication.com/#id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiGVla2FiMmNkZXYuYjJjbG9naW4uY29tLzM4NGIzNTdiLTIzMWUtNGY3MS1iYThhLWUxNzBlZDFiMzA1ZC92Mi4....................kLTE5NDYtNGRkYy04YWViLWY5ZDFiMWVmNWMyMSIsImF1Z
Should I be setting some sessionmanagement TP?
My policy is
<?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="tenant.onmicrosoft.com"
PolicyId="B2C_1A_signin_with_email"
PublicPolicyUri="http://tenant.onmicrosoft.com/B2C_1A_signin_with_email">
<BasePolicy>
<TenantId>tenant.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId>
</BasePolicy>
<BuildingBlocks>
<ClaimsSchema>
<!--Sample: Stores the error message for unsolicited request (a request without id_token_hint) and user not found-->
<ClaimType Id="errorMessage">
<DisplayName>Error</DisplayName>
<DataType>string</DataType>
<UserHelpText>Add help text here</UserHelpText>
<UserInputType>Paragraph</UserInputType>
</ClaimType>
</ClaimsSchema>
<ClaimsTransformations>
<!--Sample: Initiates the errorMessage claims type with the error message-->
<ClaimsTransformation Id="CreateUnsolicitedErrorMessage" TransformationMethod="CreateStringClaim">
<InputParameters>
<InputParameter Id="value" DataType="string" Value="You cannot sign-in without invitation. Please click the link in the email" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="errorMessage" TransformationClaimType="createdClaim" />
</OutputClaims>
</ClaimsTransformation>
<!--Sample: Initiates the errorMessage claims type with the error message user not found-->
<ClaimsTransformation Id="CreateUserNotFoundErrorMessage" TransformationMethod="CreateStringClaim">
<InputParameters>
<InputParameter Id="value" DataType="string" Value="You aren't registered in the system! Please contact the Help Desk on 0800 xxx yyy and ask to be added to the system" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="errorMessage" TransformationClaimType="createdClaim" />
</OutputClaims>
</ClaimsTransformation>
</ClaimsTransformations>
</BuildingBlocks>
<ClaimsProviders>
<!--Sample: This technical profile specifies how B2C should validate your token, and what claims you want B2C to extract from the token.
The METADATA value in the TechnicalProfile meta-data is required.
The “IdTokenAudience” and “issuer” arguments are optional (see later section)-->
<ClaimsProvider>
<DisplayName>My ID Token Hint ClaimsProvider</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="IdTokenHint_ExtractClaims">
<DisplayName> My ID Token Hint TechnicalProfile</DisplayName>
<Protocol Name="None" />
<Metadata>
<!--Sample action required: replace with your endpoint location -->
<Item Key="METADATA">https://teanant.b2clogin.com/tenant.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1A_SIGNUP_SIGNIN</Item>
<Item Key="issuer">https://b2cidtokenbuilder.azurewebsites.net/</Item>
</Metadata>
<OutputClaims>
<!--Sample: Read the email cliam from the id_token_hint-->
<OutputClaim ClaimTypeReferenceId="email" />
</OutputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Self Asserted</DisplayName>
<TechnicalProfiles>
<!-- Demo: Show error message-->
<TechnicalProfile Id="SelfAsserted-Error">
<DisplayName>Unsolicited error message</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>
<!-- Sample: Remove the continue button-->
<Item Key="setting.showContinueButton">false</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="errorMessage"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="errorMessage"/>
</OutputClaims>
</TechnicalProfile>
<!-- Demo: Show unsolicited error message-->
<TechnicalProfile Id="SelfAsserted-Unsolicited">
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CreateUnsolicitedErrorMessage" />
</InputClaimsTransformations>
<IncludeTechnicalProfile ReferenceId="SelfAsserted-Error" />
</TechnicalProfile>
<!-- Demo: Show user not found error message-->
<TechnicalProfile Id="SelfAsserted-UserNotFound">
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CreateUserNotFoundErrorMessage" />
</InputClaimsTransformations>
<IncludeTechnicalProfile ReferenceId="SelfAsserted-Error" />
</TechnicalProfile>
<TechnicalProfile Id="AAD-UserReadUsingEmailAddress-Hint">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
<Item Key="UserMessageIfClaimsPrincipalDoesNotExist">An account could not be found for the provided user ID.</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress" Required="true" />
</InputClaims>
<OutputClaims>
<!-- Required claims -->
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
<!-- Sample: Change the output email claims-->
<!--<OutputClaim ClaimTypeReferenceId="email" />-->
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
<OutputClaim ClaimTypeReferenceId="extension_userId" />
<OutputClaim ClaimTypeReferenceId="extension_tenantAbbreviation" />
<OutputClaim ClaimTypeReferenceId="extension_dateTimeFormat" />
</OutputClaims>
<!-- <OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="AssertAccountEnabledIsTrue" />
</OutputClaimsTransformations> -->
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<UserJourneys>
<UserJourney Id="SignInWithEmail">
<OrchestrationSteps>
<!--Sample: Read the input claims from the id_token_hint-->
<OrchestrationStep Order="1" Type="GetClaims" CpimIssuerTechnicalProfileReferenceId="IdTokenHint_ExtractClaims" />
<!-- Sample: Check if user tries to run the policy without invitation -->
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>email</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SelfAsserted-Unsolicited" TechnicalProfileReferenceId="SelfAsserted-Unsolicited" />
</ClaimsExchanges>
</OrchestrationStep>
<!--Sample: Read the user properties from the directory-->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadUsingEmailAddress" TechnicalProfileReferenceId="AAD-UserReadUsingEmailAddress-Hint"/>
</ClaimsExchanges>
</OrchestrationStep>
<!-- Sample: Check whether the user not existed in the directory -->
<OrchestrationStep Order="4" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SelfAssertedUserNotFound" TechnicalProfileReferenceId="SelfAsserted-UserNotFound" />
</ClaimsExchanges>
</OrchestrationStep>
<!--Sample: Issue an access token-->
<OrchestrationStep Order="5" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer"/>
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb"/>
</UserJourney>
</UserJourneys>
<RelyingParty>
<DefaultUserJourney ReferenceId="SignInWithEmail" />
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<!--Sample: Set the input claims to be read from the id_token_hint-->
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="correlationId" DefaultValue="{Context:CorrelationId}" />
<OutputClaim ClaimTypeReferenceId="signInNames.emailAddress" />
<OutputClaim ClaimTypeReferenceId="extension_userId" />
<OutputClaim ClaimTypeReferenceId="extension_tenantAbbreviation" />
<OutputClaim ClaimTypeReferenceId="extension_dateTimeFormat" />
<OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
</TrustFrameworkPolicy>

Azure B2C - Exception: "Claim with id 'email' was not found in the collection."

I can't get past this weird exception. Please, need your help.
Even though Application Insights (in vscode) shows that the email claim is in fact retrieved, I keep getting this error and don't make it to the Saml2Issuer orchestration step, hence, no SAML token. See the screenshot form the ApplicationInsights extension in vscode. Sensitive information screened. The claimsTransformation shown copies user email from an extension attribute into the email claim, which is to b eused in the SAML token.
RP File (irrelevant parts removed)
<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_saml" PublicPolicyUri="http://{Settings:Tenant}/B2C_1A_signup_signin_saml" DeploymentMode="Development" UserJourneyRecorderEndpoint="urn:journeyrecorder:applicationinsights">
<BasePolicy>
<TenantId>{Settings:Tenant}</TenantId>
<PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId>
</BasePolicy>
<RelyingParty>
<DefaultUserJourney ReferenceId="SignUpOrSignIn" />
<UserJourneyBehaviors>
<SingleSignOn Scope="Tenant" />
<SessionExpiryType>Absolute</SessionExpiryType>
<SessionExpiryInSeconds>86400</SessionExpiryInSeconds>
<JourneyInsights TelemetryEngine="ApplicationInsights" InstrumentationKey="{Settings:ApplicationInsightskey}" DeveloperMode="true" ClientEnabled="false" ServerEnabled="true" TelemetryVersion="1.0.0" />
<!-- Enable JavaScript-->
<ScriptExecution>Allow</ScriptExecution>
</UserJourneyBehaviors>
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="SAML2"/>
<Metadata>
<Item Key="XmlSignatureAlgorithm">Sha256</Item>
</Metadata>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surname" />
<OutputClaim ClaimTypeReferenceId="email"/>
<OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="objectId"/>
</OutputClaims>
<SubjectNamingInfo ClaimType="email" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" ExcludeAsClaim="true"/>
</TechnicalProfile>
</RelyingParty>
</TrustFrameworkPolicy>
TFE File (irrelevant parts removed)
<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>
...
...
<!--Overriding this here to add a partner claim type for SAML2-->
<ClaimType Id="email">
<DisplayName>Email Address</DisplayName>
<DataType>string</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OpenIdConnect" PartnerClaimType="email" />
<Protocol Name="SAML2" PartnerClaimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" />
</DefaultPartnerClaimTypes>
</ClaimType>
<ClaimType Id="extension_XXXX">
<DisplayName>Email Address</DisplayName>
<DataType>string</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OpenIdConnect" PartnerClaimType="extension_XXXX" />
<Protocol Name="SAML2" PartnerClaimType="extension_XXXX" />
</DefaultPartnerClaimTypes>
<UserInputType>TextBox</UserInputType>
</ClaimType>
...
</ClaimsSchema>
<ClaimsTransformations>
<!--We use this claims transformation to copy the value in extension_ExternalUserEmail claim to the
email claim after extension_XXXX is read from AAD using the AAD-UserReadWithObjectId Technical Profile.
-->
<ClaimsTransformation Id="CopyXXXXEmailAddress" TransformationMethod="CopyClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_XXXX" TransformationClaimType="inputClaim"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" TransformationClaimType="outputClaim"/>
</OutputClaims>
</ClaimsTransformation>
...
</ClaimsTransformations>
<ContentDefinitions>
...
</ContentDefinitions>
</BuildingBlocks>
<ClaimsProviders>
<ClaimsProvider>
<DisplayName>Application Insights</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="AppInsights-Common">
<DisplayName>Application Insights</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.Insights.AzureApplicationInsightsProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<!-- The ApplicationInsights instrumentation key, which you use for logging the events -->
<Item Key="InstrumentationKey">{Settings:ApplicationInsightskey}</Item>
<Item Key="DeveloperMode">true</Item>
<Item Key="DisableTelemetry ">false</Item>
</Metadata>
<InputClaims>
<!-- Properties of an event are added through the syntax {property:NAME}, where NAME is the property being added to the event. DefaultValue can be either a static value or a value that's resolved by one of the supported DefaultClaimResolvers. -->
<InputClaim ClaimTypeReferenceId="EventTimestamp" PartnerClaimType="{property:EventTimestamp}" DefaultValue="{Context:DateTimeInUtc}" />
<InputClaim ClaimTypeReferenceId="tenantId" PartnerClaimType="{property:TenantId}" DefaultValue="{Policy:TrustFrameworkTenantId}" />
<InputClaim ClaimTypeReferenceId="PolicyId" PartnerClaimType="{property:Policy}" DefaultValue="{Policy:PolicyId}" />
<InputClaim ClaimTypeReferenceId="CorrelationId" PartnerClaimType="{property:CorrelationId}" DefaultValue="{Context:CorrelationId}" />
<InputClaim ClaimTypeReferenceId="Culture" PartnerClaimType="{property:Culture}" DefaultValue="{Culture:RFC5646}" />
</InputClaims>
</TechnicalProfile>
<TechnicalProfile Id="AppInsights-SignInRequest">
<InputClaims>
<!-- An input claim with a PartnerClaimType="eventName" is required. This is used by the AzureApplicationInsightsProvider to create an event with the specified value. -->
<InputClaim ClaimTypeReferenceId="EventType" PartnerClaimType="eventName" DefaultValue="SignInRequest" />
</InputClaims>
<IncludeTechnicalProfile ReferenceId="AppInsights-Common" />
</TechnicalProfile>
<TechnicalProfile Id="AppInsights-UserSignUp">
<InputClaims>
<InputClaim ClaimTypeReferenceId="EventType" PartnerClaimType="eventName" DefaultValue="UserSignUp" />
</InputClaims>
<IncludeTechnicalProfile ReferenceId="AppInsights-Common" />
</TechnicalProfile>
<TechnicalProfile Id="AppInsights-SignInComplete">
<InputClaims>
<InputClaim ClaimTypeReferenceId="EventType" PartnerClaimType="eventName" DefaultValue="SignInComplete" />
<InputClaim ClaimTypeReferenceId="federatedUser" PartnerClaimType="{property:FederatedUser}" DefaultValue="false" />
<InputClaim ClaimTypeReferenceId="parsedDomain" PartnerClaimType="{property:FederationPartner}" DefaultValue="Not Applicable" />
<InputClaim ClaimTypeReferenceId="identityProvider" PartnerClaimType="{property:IDP}" DefaultValue="Local" />
</InputClaims>
<IncludeTechnicalProfile ReferenceId="AppInsights-Common" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Local Account</DisplayName>
<TechnicalProfiles>
...
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Azure Active Directory</DisplayName>
<TechnicalProfiles>
...
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Local Account SignIn</DisplayName>
<TechnicalProfiles>
<!-- This technical profile is used to read a local B2C user from AAD using an objectId.
I am overriding it here so as to call the CopyXXXXEmailAddress Claims Transformation, which
will copy the value in the extension_XXXX claim to the email claim. That way, when I add the email
claim to output claims, there is something in it when it is added to the claims bag. So,
when the email claim is used in the technical profile (PolicyProfile) in the relying party file
to populate the NameID attribute in the Subject Naming Info element, it is not null. If it is,
this error is thown "The relying party technical profile of policy 'B2C_1A_signup_signin_saml'
in tenant '<tenant>.onmicrosoft.com' specifies the claim type 'email' as the subject naming info
claim, but the claim is not present or is null" -->
<TechnicalProfile Id="AAD-UserReadUsingObjectId">
<OutputClaims>
<!--Read user's email from extension attribute-->
<OutputClaim ClaimTypeReferenceId="extension_XXXX"/>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CopyXXXXEmailAddress" />
</OutputClaimsTransformations>
</TechnicalProfile>
...
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Self Asserted</DisplayName>
<TechnicalProfiles>
...
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<UserJourneys>
<UserJourney Id="SignUpOrSignIn">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="MyOrgExchange" />
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
</ClaimsProviderSelections>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Check if the user has selected to sign in using one of the social providers -->
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="MyOrgExchange" TechnicalProfileReferenceId="OIDC-MyOrg" />
<ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail" />
<ClaimsExchange Id="TrackSignInRequest" TechnicalProfileReferenceId="AppInsights-SignInRequest" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- For social IDP authentication, attempt to find the user account in the directory. -->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>localAccountAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadUsingAlternativeSecurityId" TechnicalProfileReferenceId="AAD-UserReadUsingAlternativeSecurityId-NoError" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- This step reads any user attributes that we may not have received when authenticating using ESTS so they can be sent
in the token. -->
<OrchestrationStep Order="4" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>socialIdpAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
...
<OrchestrationStep Order="10" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="Saml2AssertionIssuer" />
<!-- Track that we have successfully sent a token -->
<OrchestrationStep Order="11" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="TrackSignInComplete" TechnicalProfileReferenceId="AppInsights-SignInComplete" />
</ClaimsExchanges>
</OrchestrationStep>
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
</UserJourneys>
</TrustFrameworkPolicy>

Azure Active Directory B2C Custom Invite Policy - Passing Custom Claims Between Steps

I have implemented an invite policy (invite users to the site by sending them an email link) via this example https://github.com/azure-ad-b2c/samples/tree/master/policies/invite
I have this user journey
<UserJourney Id="SignUpInvitation">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="GetClaims" CpimIssuerTechnicalProfileReferenceId="IdTokenHint_ExtractClaims" />
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>email</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SelfAsserted-Unsolicited" TechnicalProfileReferenceId="SelfAsserted-Unsolicited"/>
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSignUpWithReadOnlyEmail" TechnicalProfileReferenceId="LocalAccountSignUpWithReadOnlyEmail"/>
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="4" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="5" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer"/>
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb"/>
</UserJourney>
In the first step, I extract the claims from the JWT. An email and a custom claim,
<TechnicalProfiles>
<TechnicalProfile Id="IdTokenHint_ExtractClaims">
<DisplayName> My ID Token Hint TechnicalProfile</DisplayName>
<Protocol Name="None" />
<Metadata>
<Item Key="METADATA">https://mywebsite.com/internal/v1/invitation/.well-known/openid-configuration</Item>
</Metadata>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" />
<OutputClaim ClaimTypeReferenceId="extension_DBId" DefaultValue="1" />
</OutputClaims>
</TechnicalProfile>
</TechnicalProfiles>
(Note that I set a default value of 1 to my custom claim)
Then, I have the technical profile that writes the user to the directory
<TechnicalProfile Id="LocalAccountSignUpWithReadOnlyEmail">
<DisplayName>Email 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>
<!-- Sample: Remove sign-up email verification -->
<Item Key="EnforceEmailVerification">False</Item>
</Metadata>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CopyEmailAddress" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="ReadOnlyEmail" />
<InputClaim ClaimTypeReferenceId="extension_DBId" DefaultValue="2" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="ReadOnlyEmail" 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" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonEmail-Custom" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
(Note that here I set a default value of 2 to my custom claim)
AAD-UserWriteUsingLogonEmail-Custom is a modification of the built-in (inside TrustFrameworkBase.xml) AAD-UserWriteUsingLogonEmail profile, I've modified it to include my custom claim extension_DBId as a persisted claim:
<PersistedClaim ClaimTypeReferenceId="extension_DBId" DefaultValue="3" />
Here is the problem,
I generate a JWT with some arbitrary extension_DBId (Not 1,2 or 3)
But when I run the policy with it, in the output claims, I get extension_DBId=2
Which indicates that the third orchestration step (LocalAccountSignUpWithReadOnlyEmail) didn't receive the extension_DBId claim from the first orchestration step (IdTokenHint_ExtractClaims).
Why doesn't the IdTokenHint_ExtractClaims profile transfer the extension_DBId claim to the LocalAccountSignUpWithReadOnlyEmail profile?
I tried to remove the second step (SelfAsserted-Unsolicited), Still didn't work.
What could it be?
For a user journey to receive an incoming claim from the input JWT, you must add an <InputClaim /> to the relying party <TechnicalProfile />:
<RelyingParty>
<TechnicalProfile Id="PolicyProfile">
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
<InputClaim ClaimTypeReferenceId="extension_DBId" />
</InputClaims>
</TechnicalProfile>
</RelyingParty>
If the claim type of the internal claim (e.g. extension_DBId) is different to that of the input JWT (e.g. DBId), then you can add the PartnerClaimType attribute to the InputClaim element:
<InputClaim ClaimTypeReferenceId="extension_DBId" PartnerClaimType="DBId" />
#ChrisPadgett pointed out that I have to add my custom claim(s) (extension_DBId) to the InputClaims in relying party policy
<RelyingParty>
<DefaultUserJourney ReferenceId="SignUpInvitation" />
<UserJourneyBehaviors>
<ContentDefinitionParameters>
<Parameter Name="ui_locales">{Culture:RFC5646}</Parameter>
</ContentDefinitionParameters>
<ScriptExecution>Allow</ScriptExecution>
</UserJourneyBehaviors>
<TechnicalProfile Id="PolicyProfile">
<DisplayName>PolicyProfile</DisplayName>
<Protocol Name="OpenIdConnect" />
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
<InputClaim ClaimTypeReferenceId="DBId" /> <!-- This was added -->
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="emails" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub"/>
<OutputClaim ClaimTypeReferenceId="extension_DBId" />
<OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
(I then have a claim transformation that converts DBId to extension_DBId)

Can Azure-AD B2C use a mobile telephone number as a username?

We have a mobile app and website. We would like to use Azure AD-B2C for authentication. We are not going to allow any third party authentication, but instead just use Azure AD in a separate domain.
We would prefer to use the user's telephone number. My research suggests that this feature is requested, but does not exist at this time.
Are there any workarounds for this, or has this feature been implemented and I missed it?
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
I could use a mobile telephone number to sign up as the username for the local in the built-in sign-in or sign-up policy. The process like the following:
And the result is:
Or you could also use the custom policy to make this just like Chris Padgett said.

Resources