Replace Azure B2C verification code by link in email during password reset - azure-ad-b2c

I have a password reset flow. I'm sending custom email templates using sendgrid. Now I want to change the verification code with a "verification link".
I found a sample for signup invitation that is similar but not really a sample for password reset.
Does somebody already tried this?
Thanks

If you understood the flow in the signup invitation, you could use the same logic to implement a password reset flow.
Instead of sending a link to the signup invitation flow, send an email with a link to your custom password reset flow.
In the user journey, After extracting the email from Id token hint, use a technical profile to read user details.
You can always add an extra layer of security by asking the user to do a multi-factor authentication using phone or email.
After that it's the same steps from the password reset policy to display the password collection page and save the updated password.
The user journey would roughly look something like this
<UserJourney Id="PasswordReset">
<OrchestrationSteps>
<!-- Track that we have received an authentication request -->
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="TrackAuthenticationRequest" TechnicalProfileReferenceId="AzureInsights-AuthenticationRequest" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="GetClaims" CpimIssuerTechnicalProfileReferenceId="IdTokenHint_ExtractClaims" />
<OrchestrationStep Order="3" 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="4" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="UserReadUsingEmailAddressExchange" TechnicalProfileReferenceId="AAD-UserReadUsingEmailAddress" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Track that the authentication step is completed -->
<OrchestrationStep Order="5" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="TrackAuthenticationComplete" TechnicalProfileReferenceId="AzureInsights-AuthenticationComplete" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Optional MFA steps 6 to 8. Replace with email mfa if needed -->
<!-- Track that the mutifactor authentication step is initiated -->
<OrchestrationStep Order="6" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="TrackMultifactorInitiate" TechnicalProfileReferenceId="AzureInsights-MultifactorInitiate" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="7" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="PhoneFactor-Verify" TechnicalProfileReferenceId="PhoneFactor-InputOrVerify" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Track that the mutifactor authentication step is completed -->
<OrchestrationStep Order="8" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="TrackMultifactorComplete" TechnicalProfileReferenceId="AzureInsights-MultifactorComplete" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Track that the password reset flow is started-->
<OrchestrationStep Order="9" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="TrackPasswordResetInitiate" TechnicalProfileReferenceId="AzureInsights-PasswordResetInitiate" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="10" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="NewCredentials" TechnicalProfileReferenceId="LocalAccountWritePasswordUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Track that the password reset flow is completed-->
<OrchestrationStep Order="11" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="TrackPasswordResetComplete" TechnicalProfileReferenceId="AzureInsights-PasswordResetComplete" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="12" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="13" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
<!-- Track that we have successfully sent a token -->
<OrchestrationStep Order="14" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="TrackTokenIssued" TechnicalProfileReferenceId="AzureInsights-TokenIssued" />
</ClaimsExchanges>
</OrchestrationStep>
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
In the BuildUrl Method from HomeController in API, change the policy id from app settings with your custom password reset policy id
private string BuildUrl(string token)
{
string nonce = Guid.NewGuid().ToString("n");
return string.Format(this.AppSettings.B2CSignUpUrl,
this.AppSettings.B2CTenant,
this.AppSettings.B2CPasswordResetPolicy,
this.AppSettings.B2CClientId,
Uri.EscapeDataString(this.AppSettings.B2CRedirectUri),
nonce) + "&id_token_hint=" + token;
}
Note: The BuildUrl method may have to be modified once you are using custom domains in production

Related

azure AD B2C starter pack, for edit profile custom policy,if the user is signed in, authentication is skipped, how to force the user to authenticate?

this is the journey
<UserJourney Id="ProfileEdit">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsProviderSelection" ContentDefinitionReferenceId="api.idpselections">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="LocalAccountSigninEmailExchange" />
</ClaimsProviderSelections>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="4" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="B2CUserProfileUpdateExchange" TechnicalProfileReferenceId="SelfAsserted-ProfileUpdate" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="5" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
this is the link
https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack/blob/main/LocalAccounts/TrustFrameworkBase.xml
In the starter pack, the single sign-on behaviour is not configured.
By default it uses "Tenant" scope: https://learn.microsoft.com/en-us/azure/active-directory-b2c/session-behavior?pivots=b2c-custom-policy#configure-azure-ad-b2c-session-behavior.
This means the single sign-on session is active in the ProfileEdit policy.
You can configure it to be Policy scoped in ProfileEdit policy:
<!-- inside RelyingParty element, under DefaultUserJourney -->
<UserJourneyBehaviors>
<SingleSignOn Scope="Policy" />
</UserJourneyBehaviors>
You could also set it to Suppressed to disable single sign-on in the ProfileEdit policy.

Difference between CombinedSignInAndSignUp with api.signuporsignin vs ClaimsProviderSelection with api.idpselections

I'm wondering if there is any difference between following sets of orchestration steps:
CombinedSignInAndSignUp with api.signuporsignin
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="GoogleExchange" />
<ClaimsProviderSelection TargetClaimsExchangeId="FacebookExchange" />
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
</ClaimsProviderSelections>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSigninOnly-Email" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="GoogleExchange" TechnicalProfileReferenceId="Google-OAuth2" />
<ClaimsExchange Id="FacebookExchange" TechnicalProfileReferenceId="Facebook-OAuth2" />
</ClaimsExchanges>
</OrchestrationStep>
api.signuporsignin is urn:com:microsoft:aad:b2c:elements:contract:unifiedssp:2.1.5
SelfAsserted-LocalAccountSigninOnly-Email is configured with:
<Item Key="setting.showSignupLink">False</Item>
<Item Key="setting.forgotPasswordLinkLocation">none</Item>
so the intention is to sign in only.
This renders a sign in page where user can sign in directly with local account or select external idp provider to sign in with.
ClaimsProviderSelection with api.idpselection
<OrchestrationStep Order="1" Type="ClaimsProviderSelection" ContentDefinitionReferenceId="api.idpselections">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="GoogleExchange" />
<ClaimsProviderSelection TargetClaimsExchangeId="FacebookExchange" />
<ClaimsProviderSelection TargetClaimsExchangeId="LocalAccountSigninEmailExchange" />
</ClaimsProviderSelections>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSigninOnly-Email" />
<ClaimsExchange Id="GoogleExchange" TechnicalProfileReferenceId="Google-OAuth2" />
<ClaimsExchange Id="FacebookExchange" TechnicalProfileReferenceId="Facebook-OAuth2" />
</ClaimsExchanges>
</OrchestrationStep>
api.idpselection is urn:com:microsoft:aad:b2c:elements:contract:providerselection:1.2.1
This firstly render a page where user can select Local account, Google or Facebook. Then if user has selected Local account, Self asserted page to sign in is shown, and when he selected Google/Facebook, he is redirected to external idp.
Is there any difference between these two sets of orchestration steps besides slightly different user experience? From my tests it looks like in the end the result is the same - user is authenticated either via local account or external provider and I have access to the same claims in the next steps of my UserJourney.
I have already branded CombinedSignInAndSignUp with api.signuporsignin so now I'm wondering if it's safe to use this combination in different UserJourneys (ProfileUpdate, PasswordChange etc.). Samples often show ClaimsProviderSelection with api.idpselections for such UserJourneys but I do not see any difference and I wouldn't have to brand and change user experience for these UserJourneys.

Combined Azure B2C Policy Edit and Password Change

I was asked if you can have a single B2C policy that does both Profile Edit and Password Change (not SSPR).
My first thought was 'No - do two cards for that' but now that I'm thinking about it - perhaps something in the user journey could handle this
Before I go down this road, if anyone else tried and succeeded (or failed) that would save some cycles. I try to avoid 'recreating the wheel' or 'hitting dead-ends' unnecessarily..
I tried to implement the scenario and I able to do it. Below are the User Journey Code, before that you need to add one claimtype and one technical profile as below.
Claim type indicates whether user changing the password
<ClaimType Id="isPasswordChangeFlow">
<DisplayName>Indicates whether user changing the password</DisplayName>
<DataType>boolean</DataType>
<AdminHelpText>Add help text here</AdminHelpText>
</ClaimType>
Technical Profile setting the value of the isPasswordChangeFlow output claim to true
<TechnicalProfile Id="StartPasswordChangeFlow">
<DisplayName>Change Your Password</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="isPasswordChangeFlow" DefaultValue="true" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
User Journey
<UserJourney Id="PasswordChangeOrProfileEdit">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsExchange" >
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="ClaimsProviderSelection" ContentDefinitionReferenceId="api.idpselections">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="ProfileEdit" />
<ClaimsProviderSelection TargetClaimsExchangeId="StartPasswordChangeFlowExchange" />
</ClaimsProviderSelections>
</OrchestrationStep>
<OrchestrationStep Order="4" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="ProfileEdit" TechnicalProfileReferenceId="SelfAsserted-ProfileUpdate" />
<ClaimsExchange Id="StartPasswordChangeFlowExchange" TechnicalProfileReferenceId="StartPasswordChangeFlow" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="5" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>isPasswordChangeFlow</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="PasswordChangeExchange" TechnicalProfileReferenceId="LocalAccountWritePasswordUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="6" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId1" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="7" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
Make Sure to remove prompt=login parameter from the generated URL to maintain the session.
Reference Link
Output

Azure AD B2C TOTP sample

Trying to get the B2C TOTP sample working and having issues uploading the custom policy files. (github repo here: github azure b2c totp sample)
I started with the TrustFrameworkBase.xml from the SocialAndLocalAccounts policy starter pack. Added my tenant in the appropriate places and uploaded - success. Next the TrustFrameworkExtensions.xml from the github repo - created the WebApp-GraphAPI-DirectoryExtensions application as indicated in the docs - plus the IdentityExperienceFramework app and the ProxyIdentityExperienceFramework app. Placed the ID's in the appropriate places in the extensions policy file and uploaded it - I receive the following error:
Validation failed: 2 validation error(s) found in policy "B2C_1A_TOTP_TRUSTFRAMEWORKEXTENSIONS" of tenant "______test.onmicrosoft.com".User journey "SignUpOrSignIn" in policy "B2C_1A_TOTP_TrustFrameworkExtensions" of tenant ""______test.onmicrosoft.com" has step 5 with 2 claims exchanges. It must be preceded by a claims provider selection in order to determine which claims exchange can be used.User journey "SignUpOrSignIn" in policy "B2C_1A_TOTP_TrustFrameworkExtensions" of tenant "______test.onmicrosoft.com" has step 6 with 2 claims exchanges. It must be preceded by a claims provider selection in order to determine which claims exchange can be used.
Any pointers as to what I am missing?
Added SignUpOrSignIn user journey as requested:
<UserJourney Id="SignUpOrSignIn">
<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>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="FacebookExchange" TechnicalProfileReferenceId="Facebook-OAUTH" />
<ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail" />
</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>
<!-- 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="4" 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>
<!-- 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="5" 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>
<!-- Demo: The following orchestration step is always executed.
This step reads any user attributes that we may not have received when authenticating using ESTS so they
include the AppCode MFA attributes
in the token. -->
<OrchestrationStep Order="6" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Demo: The following orchestration step is executed only for unregistered
accounts (new created account or if user cancel the sign-up process).
It generates a verification app secret key for the user (none interactive step). -->
<OrchestrationStep Order="7" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>extension_StrongAuthenticationAppSecretKey</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AppFactorGenerateTotpWebHook" TechnicalProfileReferenceId="AppFactor-GenerateTotpWebHook" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Demo: The following orchestration step is executed only for unregistered
accounts (new created account or if user cancel the sign-up process).
It registers a verification app through QR code that mobile authentication app should scan. -->
<OrchestrationStep Order="8" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>strongAuthenticationAppQRCodeBitmap</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AppFactorRegisterExchange" TechnicalProfileReferenceId="AppFactor-Register" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Demo: The following orchestration step is executed only for registered accounts.
It asks the user to provide the TOTP code and verifies the provided code (using validation technical profile). -->
<OrchestrationStep Order="9" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>strongAuthenticationAppQRCodeBitmap</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AppFactorChallengeExchange" TechnicalProfileReferenceId="AppFactor-Challenge" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Demo: The following orchestration step is always executed.
It updates the verification app time step matched for a given user in the Azure Active Directory.
An error is raised if the user does not exist. -->
<OrchestrationStep Order="10" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADWriteUserAppCodeByObjectId" TechnicalProfileReferenceId="AAD-WriteUserAppCodeByObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="11" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
<ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
This happens when you have 2 user journeys with the same Id.

How can I get a forgot password link on a custom Sign In policy for local accounts?

Using AAD B2C identity experience framework, I am trying to create a custom policy that allows sign-in to a local AADB2C account and includes a "Can’t access your account?" link invoking the built-in self-service functionality. So basically I want the user experience/functionality of the built-in "B2C_1_SignIn_Local" policy, but as part of my suite of custom policies. I do NOT want this userjourney/experience to allow the user to choose a different IdP.
I have been able to create a userjourney that invokes a local sign-in, but the UI doesn't include the "Can’t access your account?" link. The userjourney I have so far looks like this:
<UserJourney Id="SignInB2CLocal">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsProviderSelection" ContentDefinitionReferenceId="api.idpselections">
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="SignInWithLogonNameExchange" />
</ClaimsProviderSelections>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="SignInWithLogonNameExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" 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="4" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
</UserJourney>
The above is based on a combination of what I found in the built-in policy B2C_1_SignIn_Local (downloaded file) and what I used for other IdPs like AAD. I also tried directly calling the built-in B2C_1_SignIn_Local from my MVC ASP.NET application but got token validation errors.
Is my intended result possible?
thanks!
Martin
You can either:
(i) Change from the ClaimsProviderSelection and ClaimsExchange orchestration steps to the CombinedSignInAndSignUp one with the api.signuporsignin content definition, or
(ii) Change from the api.selfasserted content definition to the api.signuporsignin one for the SelfAsserted-LocalAccountSignin-Email technical profile.
What is important is the <DataUri /> value for the content definition is set to:
urn:com:microsoft:aad:b2c:elements:unifiedssp:1.0.0
which renders the "Forgot your password" and "Sign up now" links.
i) The CombinedSignInAndSignUp orchestration step
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signupsignin">
<ClaimsProviderSelections>
<ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
</ClaimsProviderSelections>
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
</ClaimsExchanges>
</OrchestrationStep>
<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>
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="4" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
ii) The LocalAccountSigninEmailExchange technical profile
<TechnicalProfiles>
<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Email">
<Metadata>
<Item Key="ContentDefinitionReferenceId">api.signuporsignin</Item>
</Metadata>
</TechnicalProfile>
</TechnicalProfiles>
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
</ClaimsExchanges>
</OrchestrationStep>
<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>
<OrchestrationStep Order="3" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="4" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
</OrchestrationSteps>
This is untested but I am pretty sure you change:
ContentDefinitionReferenceId="api.idpselections"
To:
ContentDefinition Id="api.localaccountsignin"
This should just show the local account sign in page with no registration and the password reset.

Resources