Custom policy ends with an error 500 on oauth2/authresp - azure-active-directory

I want to have 2 login providers for my app. Customers would connect with B2C and employees woudl connect with our AAD by SSO. Currently the B2C login for customers works with a SignIn V2 user flow, and our SSO works just fine for any other applications.
I followed these 2 pages to get started, using the exact same names:
https://learn.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-get-started-custom
https://learn.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-setup-aad-custom
They could be clearer but I think I got everything right as far as the XML goes. When I run my custom policy, I get a page with a login form and a button to connect with the AD. If I click the button, I'm redirected to the SSO page and I log in with my user. The first time I'm asked to accept the permissions. So far so good, but after that I get redirected to https://mytenant.b2clogin.com/mytenant.onmicrosoft.com/oauth2/authresp, which gives a generic error 500 page. In the B2C Audit log, I see an event "Federate with an identity provider" with "Status: success" for the same datetime as my login so I believe the login works. Similarly, I can see a successful sign-in in the user's page in the AAD.
Is there something more I need to do that the MSDN pages missed? I should be getting redirected to jwt.ms with a token.
Relevant xml files (redacted):
TrustFrameworkBase.xml (only the parts I've modified):
<?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="mytenant.onmicrosoft.com"
PolicyId="B2C_1A_TrustFrameworkBase"
PublicPolicyUri="http://mytenant.onmicrosoft.com/B2C_1A_TrustFrameworkBase">
<!-- snip default building blocks -->
<ClaimsProviders>
<ClaimsProvider>
<DisplayName>Local Account SignIn</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="login-NonInteractive">
<DisplayName>Local Account SignIn</DisplayName>
<Protocol Name="OpenIdConnect" />
<Metadata>
<Item Key="UserMessageIfClaimsPrincipalDoesNotExist">We can't seem to find your account</Item>
<Item Key="UserMessageIfInvalidPassword">Your password is incorrect</Item>
<Item Key="UserMessageIfOldPasswordUsed">Looks like you used an old password</Item>
<Item Key="ProviderName">https://sts.windows.net/mytenantguid/</Item>
<Item Key="METADATA">https://login.microsoftonline.com/mytenant.onmicrosoft.com/.well-known/openid-configuration</Item>
<Item Key="authorization_endpoint">https://login.microsoftonline.com/mytenant.onmicrosoft.com/oauth2/token</Item>
<Item Key="response_types">id_token</Item>
<Item Key="response_mode">query</Item>
<Item Key="scope">email openid</Item>
<!-- Policy Engine Clients -->
<Item Key="UsePolicyInRedirectUri">false</Item>
<Item Key="HttpBinding">POST</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="username" Required="true" />
<InputClaim ClaimTypeReferenceId="password" Required="true" />
<InputClaim ClaimTypeReferenceId="grant_type" DefaultValue="password" />
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="openid" />
<InputClaim ClaimTypeReferenceId="nca" PartnerClaimType="nca" DefaultValue="1" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="oid" />
<OutputClaim ClaimTypeReferenceId="tenantId" PartnerClaimType="tid" />
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />
<OutputClaim ClaimTypeReferenceId="surName" PartnerClaimType="family_name" />
<OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="name" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" PartnerClaimType="upn" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
</OutputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<!-- snip other default claim providers-->
<!-- snip default user journeys-->
</TrustFrameworkPolicy>
TrustFrameworkExtension.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="mytenant.onmicrosoft.com"
PolicyId="B2C_1A_TrustFrameworkExtensions"
PublicPolicyUri="http://mytenant.onmicrosoft.com/B2C_1A_TrustFrameworkExtensions">
<BasePolicy>
<TenantId>mytenant.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkBase</PolicyId>
</BasePolicy>
<BuildingBlocks>
</BuildingBlocks>
<ClaimsProviders>
<ClaimsProvider>
<DisplayName>Local Account SignIn</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="login-NonInteractive">
<Metadata>
<Item Key="client_id">ProxyIdentityExperienceFramework_AppId</Item>
<Item Key="IdTokenAudience">IdentityExperienceFramework_AppId</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="client_id" DefaultValue="ProxyIdentityExperienceFramework_AppId" />
<InputClaim ClaimTypeReferenceId="resource_id" PartnerClaimType="resource" DefaultValue="IdentityExperienceFramework_AppId" />
</InputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<Domain>Mycompany</Domain>
<DisplayName>Login using Mycompany</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="MycompanyProfile">
<DisplayName>Mycompany Employee</DisplayName>
<Description>Login with your Mycompany account</Description>
<Protocol Name="OpenIdConnect"/>
<OutputTokenFormat>JWT</OutputTokenFormat>
<Metadata>
<Item Key="METADATA">https://login.windows.net/mytenant.onmicrosoft.com/.well-known/openid-configuration</Item>
<Item Key="ProviderName">https://sts.windows.net/mytenantguid/</Item>
<Item Key="client_id">AzureADB2CApp_AppdId</Item>
<Item Key="IdTokenAudience">AzureADB2CApp_AppdId</Item>
<Item Key="UsePolicyInRedirectUri">false</Item>
<Item Key="response_types">code</Item>
<Item Key="scope">openid</Item>
<Item Key="response_mode">form_post</Item>
<Item Key="HttpBinding">POST</Item>
</Metadata>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_MycompanySecret"/>
</CryptographicKeys>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="issuerUserId" PartnerClaimType="oid"/>
<OutputClaim ClaimTypeReferenceId="tenantId" PartnerClaimType="tid"/>
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />
<OutputClaim ClaimTypeReferenceId="surName" PartnerClaimType="family_name" />
<OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="name" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName"/>
<OutputClaimsTransformation ReferenceId="CreateUserPrincipalName"/>
</OutputClaimsTransformations>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<UserJourneys>
<UserJourney Id="SignUpOrSignInMycompany">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signinwithpassword">
<ClaimsProviderSelections>
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
<ClaimsProviderSelection TargetClaimsExchangeId="MycompanyExchange" />
</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="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail" />
<ClaimsExchange Id="MycompanyExchange" TechnicalProfileReferenceId="MycompanyProfile" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Show self-asserted page only if the directory does not have the user account already (i.e. we do not have an objectId).
This can only happen when authentication happened using a social IDP. If local account was created or authentication done
using ESTS in step 2, then an user account must exist in the directory by this time. -->
<OrchestrationStep Order="3" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SelfAsserted-Social" TechnicalProfileReferenceId="SelfAsserted-Social" />
</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="5" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
</UserJourneys>
</TrustFrameworkPolicy>
SignUpOrSigninMycompany.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="mytenant.onmicrosoft.com"
PolicyId="B2C_1A_signup_signin_mycompany"
PublicPolicyUri="http://mytenant.onmicrosoft.com/B2C_1A_signup_signin_mycompany">
<BasePolicy>
<TenantId>mytenant.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId>
</BasePolicy>
<RelyingParty>
<DefaultUserJourney ReferenceId="SignUpOrSignInMycompany" />
<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="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
</TrustFrameworkPolicy>
In the Identity Experience Framework panel, I run B2C_1A_signup_signin_mycompany and choose testapp1 with https://jwt.ms as the repyl url.

After reaching out for support on MSDN, which didn't help, I've redone the whole process from scratch and I get past this error though I still end up with an error :
AADB2C90037: An error occurred while processing the request. Please contact administrator of the site you are trying to access.
Which was caused by a missing outputclaim for the objectid.
What solved the problem of this question was 2 misunderstandings on my part from unclear instructions (imo).
The starter pack policies have placeholders you need to replace. Some fields look like placeholders but aren't and I had replaced them.
The claim provider given at step 3 contains transformations and OutputClaim that aren't defined in TrustFrameworkBase.xml. I had simply removed them at first but this time I found their definition on MSDN and added them.

Related

Azure AD B2C Localize forced password reset

I am using this sample: https://github.com/azure-ad-b2c/samples/blob/master/policies/force-password-reset/policy/TrustFrameworkExtensions_ForcePasswordReset.xml
In line 117 the default value for "userMsg" is defined as 'Your password has expired, please change to a new password.'
I am trying to localize this string but so far I have no clue where to specify it or what StringId/ElementId I should be using inside my LocalizedResources-Object.
Already tried to add this to my Localization:
<LocalizedResources Id="api.localaccountsignup.en">
<LocalizedStrings>
<LocalizedString ElementType="GetLocalizedStringsTransformationClaimType" StringId="email_subject"
>Contoso account email verification code</LocalizedString
>
<LocalizedString ElementType="GetLocalizedStringsTransformationClaimType" StringId="email_message"
>Thanks for verifying your account!</LocalizedString
>
<LocalizedString ElementType="GetLocalizedStringsTransformationClaimType" StringId="email_code"
>Your code is</LocalizedString
>
<LocalizedString ElementType="GetLocalizedStringsTransformationClaimType" StringId="email_signature"
>Sincerely</LocalizedString
>
</LocalizedStrings>
</LocalizedResources>
<LocalizedResources Id="api.localaccountsignup.es">
<LocalizedStrings>
<LocalizedString ElementType="GetLocalizedStringsTransformationClaimType" StringId="email_subject"
>Código de verificación del correo electrónico de la cuenta de Contoso</LocalizedString
>
<LocalizedString ElementType="GetLocalizedStringsTransformationClaimType" StringId="email_message"
>Gracias por comprobar la cuenta de
</LocalizedString>
<LocalizedString ElementType="GetLocalizedStringsTransformationClaimType" StringId="email_code"
>Su código es</LocalizedString
>
<LocalizedString ElementType="GetLocalizedStringsTransformationClaimType" StringId="email_signature"
>Atentamente</LocalizedString
>
</LocalizedStrings>
</LocalizedResources>
Please give me some advice ❤️
I tried to reproduce the same in my environment to reset the force password while user Signing to the application
Note: Force password expired should be in sign_in_page, not signup process.
*Kindly modify the Policy ID & PublicPolicyUri according to your domain and upload the same xml file to your B2C Tenant. *
TrustFrameworkExtensions_ForcePasswordReset.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="Yourtenant.onmicrosoft.com"
PolicyId="B2C_1A_Demo_TrustFrameworkExtensions_ForcePasswordReset"
PublicPolicyUri="http://yourtenant.onmicrosoft.com/B2C_1A_Demo_TrustFrameworkExtensions_ForcePasswordReset">
<BasePolicy>
<TenantId>ThejeshInfotechB2c.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId>
</BasePolicy>
<BuildingBlocks>
<ClaimsSchema>
<ClaimType Id="forceChangePasswordNextLogin">
<DisplayName>forceChangePasswordNextLogin</DisplayName>
<DataType>boolean</DataType>
<AdminHelpText>Directory property, Whether the user password has expired</AdminHelpText>
</ClaimType>
<ClaimType Id="continueOnPasswordExpiration">
<DisplayName>continueOnPasswordExpiration</DisplayName>
<DataType>boolean</DataType>
<AdminHelpText>continue ests non-interactive upon password expiration</AdminHelpText>
</ClaimType>
<ClaimType Id="samePassword">
<DisplayName>samePassword</DisplayName>
<DataType>boolean</DataType>
<AdminHelpText>Whether user enters the same password</AdminHelpText>
</ClaimType>
<ClaimType Id="userMsg">
<DisplayName></DisplayName>
<DataType>string</DataType>
<AdminHelpText>A claim responsible for holding user messages</AdminHelpText>
<UserInputType>Paragraph</UserInputType>
</ClaimType>
</ClaimsSchema>
<ClaimsTransformations>
<!--Compare the old and new password-->
<ClaimsTransformation Id="CompareOldAndNewPassword" TransformationMethod="CompareClaims">
<InputClaims>
<InputClaim ClaimTypeReferenceId="password" TransformationClaimType="inputClaim1" />
<InputClaim ClaimTypeReferenceId="newPassword" TransformationClaimType="inputClaim2" />
</InputClaims>
<InputParameters>
<InputParameter Id="operator" DataType="string" Value="EQUAL" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="samePassword" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
<!--Assert whether the old and new passwords are same, and return the UserMessageIfClaimsTransformationBooleanValueIsNotEqual error message-->
<ClaimsTransformation Id="ThrowErrorWhenPassowrdIsSame" TransformationMethod="AssertBooleanClaimIsEqualToValue">
<InputClaims>
<InputClaim ClaimTypeReferenceId="samePassword" TransformationClaimType="inputClaim" />
</InputClaims>
<InputParameters>
<InputParameter Id="valueToCompareTo" DataType="boolean" Value="false" />
</InputParameters>
</ClaimsTransformation>
</ClaimsTransformations>
</BuildingBlocks>
<ClaimsProviders>
<ClaimsProvider>
<DisplayName>Local Account SignIn</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="login-NonInteractive">
<InputClaims>
<!-- Continue if the password is expired -->
<InputClaim ClaimTypeReferenceId="continueOnPasswordExpiration" DefaultValue="true" />
</InputClaims>
<OutputClaims>
<!-- Indicates whether user needs to reset the password.
If the value of this claim is true, other claims aren't return-->
<OutputClaim ClaimTypeReferenceId="forceChangePasswordNextLogin" PartnerClaimType="passwordExpired" />
</OutputClaims>
</TechnicalProfile>
<!--Assert whether the new password is different than the old one. -->
<TechnicalProfile Id="ThrowErrorWhenPassowrdIsSame">
<DisplayName>Assert New Password is different</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="samePassword" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CompareOldAndNewPassword" />
<OutputClaimsTransformation ReferenceId="ThrowErrorWhenPassowrdIsSame" />
</OutputClaimsTransformations>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Local Account</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Email">
<OutputClaims>
<!-- Bubble up the forceChangePasswordNextLogin claim (return by the login-NonInteractive) to the user journey-->
<OutputClaim ClaimTypeReferenceId="forceChangePasswordNextLogin" />
</OutputClaims>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Self Asserted</DisplayName>
<TechnicalProfiles>
<!--Password reset-->
<TechnicalProfile Id="SelfAsserted-ForcePasswordReset-ExpiredPassword">
<DisplayName>Password Expired</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="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Please enter a different password</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="userMsg" DefaultValue="Your password has expired, please change to a new password." />
</InputClaims>
<DisplayClaims>
<DisplayClaim ClaimTypeReferenceId="userMsg" />
<DisplayClaim ClaimTypeReferenceId="password" Required="true" />
<DisplayClaim ClaimTypeReferenceId="newPassword" Required="true" />
<DisplayClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
</DisplayClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
</OutputClaims>
<ValidationTechnicalProfiles>
<!-- 1) validate the old password. 2) Assert whether the new password is different than the old one.
3) get the user object ID 4) persist the new password to the directory, and reset the force reset password next logon. -->
<ValidationTechnicalProfile ReferenceId="login-NonInteractive" />
<ValidationTechnicalProfile ReferenceId="ThrowErrorWhenPassowrdIsSame" />
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingSignInName" />
<ValidationTechnicalProfile ReferenceId="AAD-UserWritePasswordUsingObjectId-ResetNextLogin" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Azure Active Directory</DisplayName>
<TechnicalProfiles>
<!--Read user objectId by signInName-->
<TechnicalProfile Id="AAD-UserReadUsingSignInName">
<Metadata>
<Item Key="Operation">Read</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="signInNames.emailAddress" Required="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
<!--Save the new password to the directory-->
<TechnicalProfile Id="AAD-UserWritePasswordUsingObjectId-ResetNextLogin">
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="forceChangePasswordNextLogin" PartnerClaimType="passwordProfile.forceChangePasswordNextLogin" DefaultValue="false" AlwaysUseDefaultValue="true" />
</PersistedClaims>
<IncludeTechnicalProfile ReferenceId="AAD-UserWritePasswordUsingObjectId" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<UserJourneys>
<UserJourney Id="SignUpOrSignIn_Custom">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="FacebookExchange" />
<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>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<!-- Skip this step if change password is required. -->
<Value>forceChangePasswordNextLogin</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="FacebookExchange" TechnicalProfileReferenceId="Facebook-OAUTH" />
<ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>forceChangePasswordNextLogin</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<!--Force password reset upon password expiration-->
<ClaimsExchange Id="ForcePasswordResetUponPasswordExpiration" TechnicalProfileReferenceId="SelfAsserted-ForcePasswordReset-ExpiredPassword" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- For social IDP authentication, attempt to find the user account in the directory. -->
<OrchestrationStep Order="4" 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>
<!-- Show self-asserted page only if the directory does not have the user account already (i.e. we do not have an objectId).
This can only happen when authentication happened using a social IDP. If local account was created or authentication done
using ESTS in step 2, then an user account must exist in the directory by this time. -->
<OrchestrationStep Order="5" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SelfAsserted-Social" TechnicalProfileReferenceId="SelfAsserted-Social" />
</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="6" 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>
<!-- The previous step (SelfAsserted-Social) could have been skipped if there were no attributes to collect
from the user. So, in that case, create the user in the directory if one does not already exist
(verified using objectId which would be set from the last step if account was created in the directory. -->
<OrchestrationStep Order="7" 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="8" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
</UserJourneys>
</TrustFrameworkPolicy>
SignUp_SignIn_ForcePasswordReset.xml:
Modify the Policy ID & PublicPolicyUri in SignUp_SignIn_ForcePasswordReset.xml page with your Tenant name and upload the same to your B2C Tenant.
<?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="Tenantname.onmicrosoft.com"
PolicyId="B2C_1A_Demo_SignUp_SignIn_ForcePasswordReset"
PublicPolicyUri="http://Tenantname.onmicrosoft.com/B2C_1A_Demo_SignUp_SignIn_ForcePasswordReset">
<BasePolicy>
<TenantId>ThejeshInfotechB2c.onmicrosoft.com</TenantId>
<PolicyId>B2C_1A_Demo_TrustFrameworkExtensions_ForcePasswordReset</PolicyId>
</BasePolicy>
<RelyingParty>
<DefaultUserJourney ReferenceId="SignUpOrSignIn_Custom" />
<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="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />
<OutputClaim ClaimTypeReferenceId="forceChangePasswordNextLogin" PartnerClaimType="passwordExpired" DefaultValue="false" />
</OutputClaims>
<SubjectNamingInfo ClaimType="sub" />
</TechnicalProfile>
</RelyingParty>
</TrustFrameworkPolicy>
Once upload the both the XML files, Test the result.
Azure AD B2C > Identity Experience Framework > B2C_1A_DEMO_SIGNUP_SIGNIN_FORCEPASSWORDRESET
Once you enter the username and password, It will redirect to force password page to reset your password.
Reference: https://learn.microsoft.com/en-us/azure/active-directory-b2c/force-password-reset?pivots=b2c-custom-policy#force-password-reset-on-next-login

How to get custom B2C policy to send a oauth2 bearer (token) to my custom SignUp/SignIn API

I am trying to have my custom B2C policy to communicate with my custom SignUp/SignIn API and authenticate via oauth2 bearer (not static bearer). I have followed the instructions found here: 1 (to the letter) but unable to get it working. I cannot get the custom policy to send the Authorization header with a bearer token to my API, unsure why.
Here is my configuration:
<ClaimsProvider>
<DisplayName>REST APIs</DisplayName>
<TechnicalProfiles>
<!--OAUTH2.0 customization START-->
<TechnicalProfile Id="REST-AcquireAccessToken">
<DisplayName></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://login.microsoftonline.com/mytenant.onmicrosoft.com/oauth2/v2.0/token</Item>
<Item Key="AuthenticationType">Basic</Item>
<Item Key="SendClaimsIn">Form</Item>
</Metadata>
<CryptographicKeys>
<Key Id="BasicAuthenticationUsername" StorageReferenceId="B2C_1A_SecureRESTClientId" />
<Key Id="BasicAuthenticationPassword" StorageReferenceId="B2C_1A_SecureRESTClientSecret" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="grant_type" DefaultValue="client_credentials" />
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="https://graph.microsoft.com/.default" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="bearerToken" PartnerClaimType="access_token" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
<!--OAUTH2.0 customization END-->
<TechnicalProfile Id="REST-GetProfile">
<DisplayName>Get user extended profile Azure Function web hook</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<!-- Set the ServiceUrl with your own REST API endpoint -->
<Item Key="ServiceUrl">https://url_to_API/api/SignIn?</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="AuthenticationType">Bearer</Item>
<Item Key="UseClaimAsBearerToken">bearerToken</Item>
<Item Key="AllowInsecureAuthInProduction">false</Item>
</Metadata>
<InputClaims>
<!-- Claims sent to your REST API -->
<InputClaim ClaimTypeReferenceId="bearerToken"/>
<InputClaim ClaimTypeReferenceId="objectId" />
<InputClaim ClaimTypeReferenceId="extension_MemberId" />
<InputClaim ClaimTypeReferenceId="email" />
</InputClaims>
<OutputClaims>
<!-- Claims parsed from your REST API -->
<OutputClaim ClaimTypeReferenceId="extension_MemberId" PartnerClaimType="extension_MemberId"/>
<OutputClaim ClaimTypeReferenceId="email" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
Something that was not mentioned in the documentation, but saw on a post, is you need to add an orchestration step in the signup/signin journey to call the "get an access token" - but not 100% sure it needs it.
<OrchestrationStep Order="5" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="RESTGetAccessToken" TechnicalProfileReferenceId="REST-AcquireAccessToken" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="6" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="RESTGetProfile" TechnicalProfileReferenceId="REST-GetProfile" />
</ClaimsExchanges>
</OrchestrationStep>
I checked my API without Authorization configured and can see the custom B2C policy is calling it, but I can't see any evidence of a bearer token at all (looked at the Request.Headers in the c# code). Also, I am unsure how to configure my API Authorization side of things, that was also omitted in the documentation (unfortunately).
With the above, am I missing something? Should I see the Authorization header with a bearer token?
Any help is very much appreciated!

In Azure AD B2C, How do i link a social account with any existing local account during first time sign in from social login?

When signing in with a social account, Facebook for now, I want to check if there is a local account already - using custom policies. If so the user must sign in to that so it can be linked to the social account, and failure to do so aborts social login.
If there is no local account already just create the social account and a local account with a random password as explained here.
There is already a stackoverflow question which is fairly similar here, but it always requires signing in to a local account which I do not want, and talks about "SQL identity service" which means nothing to me.
I managed to get this working using the TechnicalProfile from wojtekdo as a starting point and carefully reading the MS documents on custom profiles with an eye to what I was trying to do. I also took the concept of userIdentities from the link provided in the comment by Jas Suri.
I did the merging of the social account with the local account using a subjourney simply to keep it separate from the main journey.
Note that I do not verify or ask for password for the local account. This I think acceptable since the user has already verified ownership of the email when creating the facebook account. Nevertheless, I would prefer to verify the local account and I have a Stackoverflow question on how to do this.
TrustFrameworkExtensions xml to achieve this:
<BuildingBlocks>
<ClaimsSchema>
<ClaimType Id="userIdentity">
<DisplayName>userIdentity</DisplayName>
<DataType>userIdentity</DataType>
<AdminHelpText>userIdentity</AdminHelpText>
<UserHelpText>userIdentity</UserHelpText>
</ClaimType>
<ClaimType Id="userIdentities">
<DisplayName>userIdentities</DisplayName>
<DataType>userIdentityCollection</DataType>
<AdminHelpText>userIdentities</AdminHelpText>
<UserHelpText>userIdentities</UserHelpText>
</ClaimType>
<ClaimType Id="issuers">
<DisplayName>issuers</DisplayName>
<DataType>stringCollection</DataType>
<UserHelpText>User identity providers. This information is received from alternativeSecurityIds</UserHelpText>
</ClaimType>
<ClaimType Id="signInNamesInfo.emailAddress">
<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>TextBox</UserInputType>
</ClaimType>
<ClaimType Id="emails">
<DisplayName>Email Addresses</DisplayName>
<DataType>stringCollection</DataType>
<AdminHelpText>Email addresses of the user.</AdminHelpText>
<UserHelpText>Your email addresses.</UserHelpText>
</ClaimType>
<ClaimType Id="strongAuthenticationEmailAddress">
<DisplayName>Email Address</DisplayName>
<DataType>string</DataType>
<AdminHelpText>Email address that the user can use for strong authentication.</AdminHelpText>
<UserHelpText>Email address to use for strong authentication.</UserHelpText>
<UserInputType>TextBox</UserInputType>
</ClaimType>
</ClaimsSchema>
<ClaimsTransformations>
<ClaimsTransformation Id="CreateEmailsFromOtherMailsAndSignInNamesInfo" TransformationMethod="AddItemToStringCollection">
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInNamesInfo.emailAddress" TransformationClaimType="item" />
<InputClaim ClaimTypeReferenceId="otherMails" TransformationClaimType="collection" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="emails" TransformationClaimType="collection" />
</OutputClaims>
</ClaimsTransformation>
<ClaimsTransformation Id="AddStrongAuthenticationEmailToEmails" TransformationMethod="AddItemToStringCollection">
<InputClaims>
<InputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" TransformationClaimType="item" />
<InputClaim ClaimTypeReferenceId="emails" TransformationClaimType="collection" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="emails" TransformationClaimType="collection" />
</OutputClaims>
</ClaimsTransformation>
<ClaimsTransformation Id="CreateSubjectClaimFromObjectID" TransformationMethod="CreateStringClaim">
<InputParameters>
<InputParameter Id="value" DataType="string" Value="Not supported currently. Use oid claim." />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="sub" TransformationClaimType="createdClaim" />
</OutputClaims>
</ClaimsTransformation>
<!-- Sample: On sign-in (first time) with social account, create a userIdentity claim, using issuerUserId and issuer name -->
<ClaimsTransformation Id="CreateUserIdentity" TransformationMethod="CreateUserIdentity">
<InputClaims>
<InputClaim ClaimTypeReferenceId="issuerUserId" TransformationClaimType="issuerUserId" />
<InputClaim ClaimTypeReferenceId="identityProvider" TransformationClaimType="issuer" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="userIdentity" TransformationClaimType="userIdentity" />
</OutputClaims>
</ClaimsTransformation>
<!--Sample: Add a userIdentity to the userIdentities collection. .-->
<ClaimsTransformation Id="AppendUserIdentity" TransformationMethod="AddItemToUserIdentityCollection">
<InputClaims>
<InputClaim ClaimTypeReferenceId="userIdentity" TransformationClaimType="item" />
<InputClaim ClaimTypeReferenceId="userIdentities" TransformationClaimType="collection" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="userIdentities" TransformationClaimType="collection" />
</OutputClaims>
</ClaimsTransformation>
<!--Sample: Extracts the list of social identity providers associated with the user -->
<ClaimsTransformation Id="ExtractIssuers" TransformationMethod="GetIssuersFromUserIdentityCollectionTransformation">
<InputClaims>
<InputClaim ClaimTypeReferenceId="userIdentities" TransformationClaimType="userIdentityCollection" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="issuers" TransformationClaimType="issuersCollection" />
</OutputClaims>
</ClaimsTransformation>
</ClaimsTransformations>
</BuildingBlocks>
<ClaimsProviders>
<ClaimsProvider>
<DisplayName>Azure Active Directory</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="AAD-ReadCommon">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
</Metadata>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="otherMails" />
<OutputClaim ClaimTypeReferenceId="strongAuthenticationEmailAddress" PartnerClaimType="signInNames.emailAddress" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateEmailsFromOtherMailsAndSignInNamesInfo" />
<OutputClaimsTransformation ReferenceId="AddStrongAuthenticationEmailToEmails" />
</OutputClaimsTransformations>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
<TechnicalProfile Id="AAD-UserReadUsingEmailAddress-NoError">
<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>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="logonIdentifier" Required="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="accountEnabled" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-ReadCommon" />
</TechnicalProfile>
<TechnicalProfile Id="AAD-AssertAccountEnabledAndCreateSubjectClaimFromObjectId">
<DisplayName>Assert account enabled</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" Required="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="accountEnabled" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
<OutputClaim ClaimTypeReferenceId="email" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="AssertAccountEnabledIsTrue" />
<OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromObjectID" />
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
<TechnicalProfile Id="AAD-UserUpdateWithUserIdentities">
<Metadata>
<Item Key="api-version">1.6</Item>
<Item Key="Operation">Write</Item>
<Item Key="RaiseErrorIfClaimsPrincipalAlreadyExists">false</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" Required="true" />
</InputClaims>
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="objectId" />
<PersistedClaim ClaimTypeReferenceId="userIdentities" />
<!--<PersistedClaim ClaimTypeReferenceId="extension_requiresMigrationBool" DefaultValue="false" AlwaysUseDefaultValue="true"/>-->
</PersistedClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="userIdentities" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="ExtractIssuers" />
</OutputClaimsTransformations>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsProvider>
<DisplayName>Facebook</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="Facebook-OAUTH">
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateUserIdentity" />
<OutputClaimsTransformation ReferenceId="AppendUserIdentity" />
</OutputClaimsTransformations>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
</ClaimsProviders>
<UserJourneys>
<UserJourney Id="SignUpOrSignIn">
<OrchestrationSteps>
<!-- For social IDP authentication, attempt to find the user account in the directory. -->
<OrchestrationStep Order="4" 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>
<!-- Find local account using email-->
<OrchestrationStep Order="5" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>localAccountAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="FindLocalAccount" TechnicalProfileReferenceId="AAD-UserReadUsingEmailAddress-NoError"/>
</ClaimsExchanges>
</OrchestrationStep>
<!-- start a subjourney to verify local account if one was found in previous step -->
<OrchestrationStep Order="6" Type="InvokeSubJourney">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>localAccountAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<JourneyList>
<Candidate SubJourneyReferenceId="MergeWithLocalAccount" />
</JourneyList>
</OrchestrationStep>
</OrchestrationSteps>
</UserJourney>
</UserJourneys>
<SubJourneys>
<SubJourney Id="MergeWithLocalAccount" Type="Call">
<OrchestrationSteps>
<!-- assert any found local account is enabled -->
<OrchestrationStep Order="1" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>localAccountAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AssertLocalAccountEnabled" TechnicalProfileReferenceId="AAD-AssertAccountEnabledAndCreateSubjectClaimFromObjectId"/>
</ClaimsExchanges>
</OrchestrationStep>
<!-- merge account with any existing and verified local account-->
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>localAccountAuthentication</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>accountVerified</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AADMergeAccount" TechnicalProfileReferenceId="AAD-UserUpdateWithUserIdentities" />
</ClaimsExchanges>
</OrchestrationStep>
</OrchestrationSteps>
</SubJourney>
</SubJourneys>
I have included all the xml I added to solve the merging social and local account, excluding everything else. I am aware this is not a minimal solution and some bits are probably not needed, but it is a working solution and might help others figure it out.
Assuming you are using email address as the user name you need to get that email address every time you sign in to that social provider. When you finalize the extenal provider exchange (be it Facebook) you need to take the received email address and make use of a technical profile which allows reading directory by email. There is a profile which does it but it throws an error when the account is not found so you need a slightly modified version, namely with RaiseErrorIfClaimsPrincipalDoesNotExist key set to false:
<TechnicalProfile Id="AAD-UserReadUsingEmailAddress-NoError">
<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>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="logonIdentifier" Required="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="accountEnabled" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="AssertAccountEnabledIsTrue" />
<OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromObjectID" />
</OutputClaimsTransformations>
<IncludeTechnicalProfile ReferenceId="AAD-ReadCommon" />
</TechnicalProfile>
After executing the profile you will know, by checking ObjectId, if the user was found or not and act accordingly.

Azure AD B2C Claim resolver: Read user details using objectId from query string

I have to prepopulate user email id in the user journey using the object id passed as query string in authorize URL.
I referred to non protocol parameter claim resolvers
But i am getting exception when i try to read using ObjectId.
TechnicalProfile,
<TechnicalProfile Id="AAD-UserReadUsingObjectIdNew">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
<Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" DefaultValue="{OAUTH-KV:objId}"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="signInName" />
<!-- Optional claims -->
<OutputClaim ClaimTypeReferenceId="displayName" />
<OutputClaim ClaimTypeReferenceId="otherMails" />
<OutputClaim ClaimTypeReferenceId="givenName" />
<OutputClaim ClaimTypeReferenceId="surname" />
<OutputClaim ClaimTypeReferenceId="country" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="extension_creationsource" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
UserJourney,
<UserJourney Id="PasswordChange">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountRead" TechnicalProfileReferenceId="AAD-UserReadUsingObjectIdNew" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountLogin" TechnicalProfileReferenceId="LocalAccountLoginUsingObjectIdAndPassword" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
URL,
https://tenant.b2clogin.com/tenant.onmicrosoft.com/oauth2/v2.0/authorize?p=B2C_1A_PasswordChange_Mob_HH_NewFlow&client_id=xxxxxx-2415-4bb9-9529-131edxxxxx&nonce=defaultNonce&redirect_uri=https%3A%2F%2Flocalhost%3A44321%2F&scope=openid&response_type=id_token&prompt=login&objId=xxxxxx-5e54-4126-9982-bexxxxxx
AppInsight,
"Statebag": {
"Complex-CLMS": {
"objectId": "{OAUTH-KV:objId}"
}
"Message": "An error occurred while retrieving User using identifier claim type \"objectId\" in tenant \"4722b4c2-6388-4c69-9cfb-f950ea762665\". Error returned was 400/Request_BadRequest: Bad request. Please fix the request before retrying."
Notes:
If i make a Rest API call and sending this ObjectId as inputclaim, It is resolving the value and I am able to get the object id value in API request body properly. But if I use it against to read from AAD, it is not resolving the value
Update:
I was trying to check with login hint works as per Link
But it didn't work for me. Then I used signinorsignup policy from startpack.
It started working for signinorsignup. So I found that,
{OIDC:LoginHint} works in only orchestration step CombinedSignInAndSignUp. It does not work in ClaimsProviderSelection.
I think that is why {OAUTH-KV:objId} is also not working for me as am using ClaimsProviderSelection
Any help will be appreciated.
Please help me to fix this.

Getting email after logged in with Azure AD on Azure AD B2C

Scenario: I'm using Angular 5 for front-end and .NET core 2.0 for back-end, MSAL.js to authenticate against Azure AD B2C in Angular SPA, then use returned id_token as Bearer Token to send requests to WebAPI endpoints.
I have successfully setup multi-tenant Azure AD as a provider in Azure AD B2C (followed the answer here Multi-tenant Azure AD in Azure AD B2C), but in the returned id_token, there's no claim for email address. Note: If I configure single-tenant Azure AD, I get back a claim with type http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress for email address, but wasn't able to do so with multi-tenant AD.
I believe the limitation is with Azure AD v2.0 that's been mentioned here: https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-limitations
Question: How do I retrieve the user's email address after log in.
I followed the guideline in this article https://monteledwards.com/2017/10/18/a-complete-integration-azure-ad-b2c-azure-ad-graph-api-logic-apps/ to add an extra logic app to resolve email from id_token, but my problem is I don't have objectId back either.
Claims I've got back after successful authentication are:
iss - https://login.microsoftonline.com/<My-B2C-Tenant-Id>/v2.0/
exp - ticks
nbf - ticks
aud - My-B2C-App-Id
name - string
http://schemas.microsoft.com/identity/claims/identityprovider - tid
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier - My-B2C-App-Id
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname - string
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname - string
nonce - GUID
http://schemas.microsoft.com/identity/claims/scope - User.Read
azp - GUID
ver - 1.0
iat - ticks
My technical profile for multi-tenant Azure AD -> Azure AD B2C is:
<TechnicalProfile Id="AzureADAccountProfile">
<DisplayName>Log in with your work account</DisplayName>
<Protocol Name="OpenIdConnect"/>
<OutputTokenFormat>JWT</OutputTokenFormat>
<Metadata>
<Item Key="authorization_endpoint">https://login.microsoftonline.com/common/oauth2/v2.0/authorize</Item>
<Item Key="client_id">My ID</Item>
<Item Key="DiscoverMetadataByTokenIssuer">true</Item>
<Item Key="HttpBinding">POST</Item>
<Item Key="IdTokenAudience">My ID</Item>
<Item Key="response_types">id_token</Item>
<Item Key="scope">openid profile</Item>
<Item Key="UsePolicyInRedirectUri">false</Item>
<Item Key="ValidTokenIssuerPrefixes">https://login.microsoftonline.com/</Item>
</Metadata>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_AzureADSecret"/>
</CryptographicKeys>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="enterpriseAuthentication" />
<OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="name" />
<OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="tid" />
<OutputClaim ClaimTypeReferenceId="socialIdpUserId" PartnerClaimType="oid" />
<OutputClaim ClaimTypeReferenceId="tenantId" PartnerClaimType="tid"/>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="unique_name" />
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />
<OutputClaim ClaimTypeReferenceId="surName" PartnerClaimType="family_name" />
<OutputClaim ClaimTypeReferenceId="tenant" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName"/>
<OutputClaimsTransformation ReferenceId="CreateUserPrincipalName"/>
<OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId"/>
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
Getting the email address that is associated with the user account differs between a consumer/personal account and an organization/work account.
Personal account
Reference: Azure Active Directory v2.0 tokens reference
The email address that is associated with the user account can be issued in the ID token.
1) Change the "scope" metadata item from "openid profile" to "openid profile email".
<Metadata>
<Item Key="scope">openid profile email</Item>
</Metadata>
2) Change the "email" output claim from:
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="unique_name" />
to:
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
Work account
The email address that is associated with the user account must be retrieved using the Microsoft Graph API.
1) Change the "AzureADAccountProfile" technical profile from "OpenIdConnect" to "OAuth2" and add the metadata items to retrieve the profile properties for the signed-in user.
Note: The "Get a user" operation doesn't return the tenant identifier of the signed-in user so the following technical profile creates the "identityProvider" claim, which is required for the alternative security identifier, from the domain part of the "userPrincipalName" property of this user.
<TechnicalProfile Id="AzureADAccountProfile">
<DisplayName>Log in with your work account</DisplayName>
<Protocol Name="OAuth2"/>
<OutputTokenFormat>JWT</OutputTokenFormat>
<Metadata>
<Item Key="AccessTokenEndpoint">https://login.microsoftonline.com/organizations/oauth2/v2.0/token</Item>
<Item Key="authorization_endpoint">https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize</Item>
<Item Key="BearerTokenTransmissionMethod">AuthorizationHeader</Item>
<Item Key="ClaimsEndpoint">https://graph.microsoft.com/v1.0/me</Item>
<Item Key="client_id"><!-- Enter your client ID --></Item>
<Item Key="DiscoverMetadataByTokenIssuer">true</Item>
<Item Key="HttpBinding">POST</Item>
<Item Key="IdTokenAudience"><!-- Enter your client ID --></Item>
<Item Key="response_types">code</Item>
<Item Key="scope">https://graph.microsoft.com/user.read</Item>
<Item Key="UsePolicyInRedirectUri">false</Item>
<Item Key="ValidTokenIssuerPrefixes">https://login.microsoftonline.com/</Item>
</Metadata>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_AzureADSecret"/>
</CryptographicKeys>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="enterpriseAuthentication" />
<OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="displayName" />
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="mail" />
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="givenName" />
<OutputClaim ClaimTypeReferenceId="surname" PartnerClaimType="surname" />
<OutputClaim ClaimTypeReferenceId="socialIdpUserId" PartnerClaimType="id" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" PartnerClaimType="userPrincipalName" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateAzureADIdentityProvider" />
<OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId" />
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName" />
<OutputClaimsTransformation ReferenceId="CreateUserPrincipalName" />
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
2) Create the "CreateAzureADIdentityProvider" claims transformation.
<ClaimsTransformation Id="CreateAzureADIdentityProvider" TransformationMethod="ParseDomain">
<InputClaims>
<InputClaim ClaimTypeReferenceId="userPrincipalName" TransformationClaimType="emailAddress" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="identityProvider" TransformationClaimType="domain" />
</OutputClaims>
</ClaimsTransformation>

Resources