Azure B2 - jit migration failing to authenticate user on password reset - all configured using custom policies - azure

So I have been involved in implementing Azure B2C using custom policies.
I've tried to follow jit v2 migration process from this git repo https://github.com/azure-ad-b2c/user-migration/tree/master/jit-migration-v2
So the legacy system is .net framework 4.6.2 and MS SQL database. I have managed to implement all functionalities needed for B2C with the help of owin libraries.
So B2C is supposed to redirect users back to our website after the forgot password process and it does redirect but the user is not authenticated. Instead, we’re getting this generic error:
"OpenIdConnectMessage.Error was not null, indicating an error. Error: 'server_error'. Error_Description (may be empty): 'AADB2C90037: An error occurred while processing the request. Please contact administrator of the site you are trying to access.
Correlation ID: d38ccb08-4c77-4716-97e0-465f0aebfeb6
Timestamp: 2021-08-09 08:34:22Z
'. Error_Uri (may be empty): 'error_uri is null'."
I need to say that forgot password process with authenticated redirect back works all fine when the user is already migrated to B2C. The problem only happens when the user is a non-migrated user. Both scenarios are using the same custom policies except some ValidationTechnicalProfile are conditional on whether the user has been migrated or not.
Here is piece of our custom policy that is responsible for password reset process:
<!-- PASSWORD RESET first page -->
<TechnicalProfile Id="PasswordResetFirstPage">
<DisplayName>Reset password using email address</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="EnforceEmailVerification">True</Item>
<Item Key="IpAddressClaimReferenceId">IpAddress</Item>
<Item Key="ContentDefinitionReferenceId">api.localaccountpasswordreset</Item>
<Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Your account has been locked. Contact your support person to unlock it, then try again.</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<IncludeInSso>false</IncludeInSso>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
</OutputClaims>
<!-- NOTE: Remove the validation technical profile to the extension policy -->
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingEmailAddress" ContinueOnError="true" />
<ValidationTechnicalProfile ReferenceId="REST-UserMigration-LocalAccount-PasswordReset1" ContinueOnError="false" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
<!-- PASSWORD RESET second page -->
<TechnicalProfile Id="PasswordResetSecondPage">
<DisplayName>Change password (username)</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.localaccountpasswordreset</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="newPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
</OutputClaims>
<!-- NOTE: Remove the validation technical profile to the extension policy -->
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserWritePasswordUsingObjectId">
<!--Don't run this validation technical profile if objectId is not exists (migrated acccount)-->
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>objectId</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
<ValidationTechnicalProfile ReferenceId="REST-UserMigration-LocalAccount-PasswordReset2">
<!--Don't run this validation technical profile if objectId is exists (existing acccount)-->
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
<ValidationTechnicalProfile ReferenceId="AAD-MigrateUserUsingLogonEmail">
<!--Don't run this validation technical profile if objectId is exists (existing acccount)-->
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
<ValidationTechnicalProfile ReferenceId="REST-UserMigration-PasswordUpdate" ContinueOnError="false" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
Generally the whole process works fine: user is migrated to B2C and password is reset but the actual redirect back to our website is just not authenticated. So far the only workaround we have managed to implement is to just redirect user back to login page but it would be nice to understand what's going on and fix it.
I also need to mention that the same migration step works fine for non-migrated users that want to sign in and there is no problem with that scenario.
We have contacted azure support and we already had few calls with them but it looks like they don't know why this happens and they have not been able to help us sort this out.
Any suggestions very appriciated.

Related

Technical Profile fails to match user account with email (signInNames.emailAddress) claim

Can anyone explain to me (before Azure B2C Custom Policies make me pull what's left of my hair out), why this technical profile fails to ever return an "objectId" when a user account exists in Azure B2C. I am collecting the email claim in a previous screen and calling the technical profile from the orchestration step.
I can see the profile executing in my Application Insights logs and I have confirmed that the email address I use in the claim is in the directory. But every time, no matter which email address I use, I never get an objectId back which means I can never detect if the user exists or not!
Technical Profile
<TechnicalProfile Id="UE-AAD-CheckAccountExistsByEmail">
<Protocol Name="Proprietary"
Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
<Item Key="UserMessageIfClaimsPrincipalDoesNotExist">An account could not be found for the provided email address.</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress" Required="true"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId"/>
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
Orchestration Step
<OrchestrationStep Order="2" Type="ClaimsExchange">
<Preconditions>
<!-- Skip this if we already have an object id from single signon -->
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectIdFromSession</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<!-- Call a technical profile to see if an account can be found with the email supplied in AD -->
<ClaimsExchange Id="AccountExistsClaim"
TechnicalProfileReferenceId="UE-AAD-CheckAccountExistsByEmail" />
</ClaimsExchanges>
</OrchestrationStep>
You have specified ClaimsTransformationProtocolProvider as the handler.
You need the AAD provider as the handler to make Graph API queries.
Though if AAD-Common already has the Protocol element, you don't need to specify it here again since it'll be included from there.
Like so:
<TechnicalProfile Id="UE-AAD-CheckAccountExistsByEmail">
<!-- You don't actually need this though if AAD-Common has it -->
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.AzureActiveDirectoryProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
<Item Key="UserMessageIfClaimsPrincipalDoesNotExist">An account could not be found for the provided email address.</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" PartnerClaimType="signInNames.emailAddress" Required="true"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId"/>
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
ClaimsTransformationProtocolProvider is used for running claims transformations to produce new claims or modify existing claims.
It is not used for querying AAD.

Azure B2C custom policy - Is there a way to display a claim in a ClaimsProviderSelection orchestration step

I am currently working with Azure B2C custom policies for my Auth flow.
I have a ClaimsProviderSelection orchestration step which shows the user two options:
Send code to their MFA email saved in authentication methods
Lost Email
What I would like to do is show the users email address through the use of a ClaimProvider in either the display text, or the button itself (see below)
If this is not possible, then I would love to be able to add a 'lost email' button on the verification control page itself - like so:
From what I have seen though, it seems this is only available with 'ForgotPasswordExchange' (as seen here: https://learn.microsoft.com/en-us/azure/active-directory-b2c/add-password-reset-policy?pivots=b2c-custom-policy) for passwords and not authentication methods.
If anyone has any experience with customizing ClaimsProviderSelection steps, or adding custom links on orchestration steps your help would be greatly appreciated!
See below for code examples:
Orchestration step:
<OrchestrationStep Order="2" Type="ClaimsProviderSelection" ContentDefinitionReferenceId='api.MFAselections' >
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>strongAuthenticationEmailAddress</Value>
<Value>strongAuthenticationPhoneNumber</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsProviderSelections>
<ClaimsProviderSelection TargetClaimsExchangeId="MFAVerifyEmailAddress" />
<ClaimsProviderSelection TargetClaimsExchangeId="LostEmailExchange" />
</ClaimsProviderSelections>
</OrchestrationStep>
Technical Profile:
<TechnicalProfile Id="MFA_VerifyEmailAddress">
<DisplayName>SEND TO {Claim:strongAuthenticationEmailAddress}
</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ContentDefinitionReferenceId">MFAVerifyEmail</Item>
<Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>
<!-- <Item Key="setting.showContinueButton">false</Item> -->
<Item Key="setting.showCancelButton">false</Item>
<Item Key="UserMessageIfSessionDoesNotExist">You have exceeded the maximum time allowed.</Item>
<Item Key="UserMessageIfMaxRetryAttempted">You have exceeded the number of retries allowed.</Item>
<Item Key="UserMessageIfInvalidCode">You have entered the wrong code.</Item>
<Item Key="UserMessageIfSessionConflict">Cannot verify the code, please try again later.</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="MFAcomplete" DefaultValue="true" AlwaysUseDefaultValue='true'/>
</InputClaims>
<DisplayClaims>
<DisplayClaim DisplayControlReferenceId="emailVerificationControl" />
</DisplayClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="MFAcomplete" DefaultValue="email" AlwaysUseDefaultValue='true' />
<OutputClaim ClaimTypeReferenceId="isLostEmail" DefaultValue="false" AlwaysUseDefaultValue='true' />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
For anyone who is coming across this - this is what I ended up doing:
Add ContentDefinitionParameters with claim to UserJourneyBehaviors in your RelyingParty
<ContentDefinitionParameters> <Parameter Name="email">{Claim:maskedEmail}</Parameter> </ContentDefinitionParameters>
Use JS to grab email claim from source code, and insert to HTML
const parser = new URL(SETTINGS.remoteResource); let email = parser.searchParams.get('email');
Have you tried to do Output Claims transformation on the email, create a claim of type string, then append the email to it, in a previous step. And display that on the screen.

how to check if user exists in AD before migration

I'm using the guide here to perform Just-In-Time migration of a user from a legacy Idp to azure ad b2c: https://github.com/azure-ad-b2c/user-migration/tree/master/jit-migration-v2. I have this working properly on its own with a service I am using to query the legacy IdP and returning expected claims.
However, I would like to modify the above to first check if the user exists in AD before trying to migrate. I have tried declaring a ValidationTechnicalProfile, but it doesn't really seem to be working:
<TechnicalProfile Id="AAD-UserCheckUsingEmailAddress">
<Metadata>
<Item Key="Operation">Read</Item>
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
<Item Key="UserMessageIfClaimsPrincipalDoesNotExist">An account could not be found for the provided user ID.</Item>
</Metadata>
<IncludeInSso>false</IncludeInSso>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="username" Required="true" />
</InputClaims>
<OutputClaims>
<!-- Required claims -->
<OutputClaim ClaimTypeReferenceId="objectId" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
Below, I use the above defined profile to check if the objectId exists in the claim before migration:
<!-- SIGN-IN -->
<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Email">
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="needToMigrate" />
</OutputClaims>
<ValidationTechnicalProfiles>
<!--First check if user exists in AD-->
<ValidationTechnicalProfile ReferenceId="AAD-UserCheckUsingEmailAddress" />
<!--Demo: Add user migration validation technical profile before login-NonInteractive.
Only execute migration if user does not exist in AD-->
<ValidationTechnicalProfile ReferenceId="REST-UserMigration-LocalAccount-SignIn" ContinueOnError="false" >
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
Change this
<InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="username" Required="true" />
To
<InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="signInNames.emailAddress" Required="true" />
And this
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
To
<Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
This assumes that the user enters their email into a textbox (or otherwise acquired) with claim name signInName, and the users identifier is stored in signInNames.emailAddress.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/active-directory-technical-profile#inputclaims
PartnerClaimType username Is not valid. There is no attribute on the user called username.
This sample does something similar
https://github.com/azure-ad-b2c/user-migration/tree/master/seamless-account-migration
https://github.com/azure-ad-b2c/user-migration/blob/master/seamless-account-migration/policy/TrustFrameworkExtensionsSeamlessMigration.xml#L52

Force user to login after ADB2C signup

When the user signup on the adb2c, I want him to type his login / password, and not being already connected.
I tried to edit the signin_signup policy but without results
In Custom Policies, you can use a self asserted page to show the user that they have successfully signed up, but not allow them to continue the journey from there. This is sometimes referred to as a "block page" in our samples. Since the user cannot continue the journey, a token will not be issued and a session will not be established.
You can instead use a Custom HTML page to allow the user to return to the home page from here.
The user then needs to login again to get an authenticated session.
The block page is shown to be used here:
https://github.com/azure-ad-b2c/samples/blob/4e43fab365e29f002e9e033a4e078bc2091a8494/policies/password-reset-only/policy/TrustFrameworkExtensions.xml#L132
<TechnicalProfile Id="SelfAsserted-BlockSignUpClaimsIssuance">
<DisplayName>BlockSignUpClaimsIssuance 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.blockSignUpPage</Item>
<!--Demo: hide the continue and cancel buttons -->
<Item Key="setting.showContinueButton">false</Item>
<Item Key="setting.showCancelButton">false</Item>
</Metadata>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="GenerateAMessage" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="userMessage" />
</InputClaims>
<OutputClaims>
<!--Demo: Show the paragraph claim with the message to the user -->
<OutputClaim ClaimTypeReferenceId="userMessage" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
Apply a precondition to this step, such that if the newUser claim exists, that this step is not skipped:
<OrchestrationStep Order="YOUR STEP #" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>newUser</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="SelfAsserted-BlockSignUpClaimsIssuance" />
</ClaimsExchanges>
</OrchestrationStep>

azure ad b2c custompolicy,givenname and surname is removed from UI form,if AAD-UserReadUsingObjectId is included as ValidationTechnicalProfile

After social login, I want to capture givenname, surname, and some other information from the user and then store the user in the directory and then pass these details to external API.
But the moment I include "AAD-UserReadUsingObjectId" in the validation profile, the givenname and surname is removed from the user form. I am not sure why does this occur as I have to include the AAD-UserReadUsingObjectId to read other custom fields
self asserted technical profile :
<TechnicalProfile Id="SelfAsserted-ObtainUserInfo">
<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.selfasserted</Item>
<Item Key="language.button_continue">Next</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="objectId"/>
<InputClaim ClaimTypeReferenceId="givenname"/>
<InputClaim ClaimTypeReferenceId="surName" />
<InputClaim ClaimTypeReferenceId="email"/>
<InputClaim ClaimTypeReferenceId="alternativeSecurityId"/>
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId"/>
<OutputClaim ClaimTypeReferenceId="givenname" Required="true"/>
<OutputClaim ClaimTypeReferenceId="surName" Required="true" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingAlternativeSecurityId" ContinueOnError="false">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>localAccountAuthentication</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingObjectId" ContinueOnError="false">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>authenticationSource</Value>
<Value>localAccountAuthentication</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>objectId</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
<ValidationTechnicalProfile ReferenceId="API-RegisterUserInExternalSystem" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>
Observation: If I comment out the givenname, surname from the output claim of AAD-UserReadUsingObjectId these fields will be visible on the form.
Use Case: User Journey where User will do Social Login and then User will be checked in AAD B2C and if not present then will prompt user to provide few more details and then add it to AAD B2C and then call REST API to add Users in External System.
Here in your solution ValidationTechnical Profile of "AAD-UserReadUsingObjectId" where you
included GivenName, SurName etc, which is the Reason "SelfAsserted-ObtainUserInfo" technical profile considering the givenName and surName claim as Read Data but not considering as UserInputType which is the reason you are not able to see it .
Let me try to Provide you Solution how you can implement:
Orchestration Step1 : Technical Profile to Login with Social.
Orchestration Step2 : Technical Profile to ReadUsingAlternativeSecurityID, from here if your social account data exist in AAD B2C then you will get objectID.
Orchestration Step3 : Precondition to check if ObjectID present or not (You can add more precondition if you want), if it is not present then run the step where you run the TechnicalProfile Id="SelfAsserted-ObtainUserInfo" and in the validation profile include "AAD-UserWriteUsingAlternativeSecurityId" and "API-RegisterUserInExternalSystem" without any PreRequisite in the ValidationTechnicalProfile.
Refer this link to get the starter pack and refer UserJourney "SignUpOrSignIn".

Resources