B2C Custom Policy - Are SubJourney OutputClaims accessible in parent UserJourney - azure-ad-b2c

There appears to be limited documentation around sub journeys. https://learn.microsoft.com/en-us/azure/active-directory-b2c/subjourneys
I have a problem where in my SubJourney, I read the user and get the object Id. In the main UserJourney, I later use that object Id to read the user again, but it complains.
Although objectId is an output claim in the first step of the SubJourney, the main User Journey cannot use that output.
<SubJourneys>
<SubJourney Id="ResetPhoneNumberOnAccount" Type="Call">
<OrchestrationSteps>
<!-- Look to see if the user exists if its a phone recovery -->
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="CheckIfUserExists" TechnicalProfileReferenceId="AAD-UserDiscoveryUsingLogonPhoneNumber-FullProfile" />
</ClaimsExchanges>
</OrchestrationStep>
<!-- Other Step -->
</SubJourney>
</SubJourneys>
AAD-UserDiscoveryUsingLogonPhoneNumber-FullProfile is defined: https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack/blob/master/scenarios/phone-number-passwordless/Phone_Email_Base.xml#L905
Are SubJourneys not able to send output claims up the chain?

Yes, output claims from orchestration steps in sub journeys are accessible from the parent user journey. It seems like when an orchestration step depends on an output claim returned from a sub journey, the step must itself be encapsulated in its own sub journey.
If AAD-UserReadUsingObjectId exists in the main journey, but objectId is only output in a sub journey, the policy will fail validation. This appears to me to be bug with the XML schema validator.
I have examined trace logs in app insights and, after applying this workaround, can confirm that claims output in sub journeys do persist until the main journey has ended.
For a more in depth look at this problem, check out my issue on GitHub.

Seems like its not possible, can you try using the transfer sub-journey here?
<SubJourneys>
<SubJourney Id="B" Type="Transfer">
<OrchestrationSteps>
...
<OrchestrationStep Order="5" Type="SendClaims">
</OrchestrationSteps>
</SubJourney>
</SubJourneys>

Related

B2C Custom Policy Nested SubJourney

The Microsoft documentation on subjourneys state the following: A sub journey is called only from a user journey, it shouldn't call another sub journey. (https://learn.microsoft.com/en-us/azure/active-directory-b2c/subjourneys#user-journey-branching)
From a development point of view it would be good to split reusable steps into subjouneys and call as required which is exactly how MS structure their policies, this is an extract of base-v1
<SubJourney Id="TotpFactor-Verify" Type="Call">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="InvokeSubJourney">
<JourneyList>
<Candidate SubJourneyReferenceId="SetTotpInitialValue" />
</JourneyList>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
The base-v1 entire setup is small reusable subjourneys that are referenced from others.
Do we know if this statement in the documentation is out of date or it's not support for customer custom policies?

B2C custom policy Change Password "The username or password provided in the request are invalid"

I'm trying to implement a password change custom policy for Azure AD B2C.
I've used this file as a base, changed the tenantid, and uploaded it to Azure.
When I try a demo from the Azure portal, it asks me to log in, then asks for an old password and a new password to change it. And it works perfectly fine. I can change my password there.
Then I've implemented a button, that redirects to the password change URL.
private redirectPasswordChange(appConfigService: AppConfigService): void {
const passwordChangeUrl = `${appConfigService.config.AD_AUTHORITY_CHANGE}?client_id=${appConfigService.config.AD_CLIENT_ID}
&nonce=defaultNonce
&redirect_uri=${appConfigService.config.BASE_URL}
&scope=openid
&response_type=id_token`;
if (isPlatformBrowser(PLATFORM_ID)) {
window.location.href = passwordChangeUrl;
} else {
this.window.location.href = passwordChangeUrl;
}
}
I've removed prompt=login from the URL because the button that is going to redirect to the password change URL is on the "My profile" section on our website. So users are already logged in, and I don't want them to log in again.
Then I tested it on our website, when I click to button, it redirects me to the password change page, it asks for my old password, new password, and confirmation of it, which is perfect for me. But the problem is, it doesn't accept my old password, it says "The username or password provided in the request are invalid".
I've searched for the problem online, including StackOverflow, but I found that there are lots of different answers for this problem. One of them is saying that:
Its because these input claims are overwriting the input claim names for login-noninteractive.
<InputClaim ClaimTypeReferenceId="grant_type" DefaultValue="client_credentials" />
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="https://{{tenantID}}.onmicrosoft.com/{{registeredApiAppName}}/.default" />
</InputClaims>
use different claim names, and use a partnerclaimtype to send the value with the original claim name.
https://stackoverflow.com/a/65931145/8831824
When I take a look, I can see that those orchestrationsteps on PasswordChange.xml are the same in TrustFrameworkBase.xml
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin"> <ClaimsProviderSelections> <ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" /> </ClaimsProviderSelections> <ClaimsExchanges> <ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" /> </ClaimsExchanges> </OrchestrationStep>
<OrchestrationStep Order="3" Type="ClaimsExchange"> <ClaimsExchanges> <ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" /> </ClaimsExchanges> </OrchestrationStep>
Is there some kind of overwriting here? I mean, they are exactly the same, but in two different files, In TrustFrameworkBase.xml, for SignUpOrSignIn journey, and PasswordChange.xml, for PasswordChange journey.
Here are the policies I have. I did put great effort to solve the problem, but I couldn't even identify it. Hope someone can give me an idea to work on it. Thanks for reading.
TrustFrameworkBase.xml: https://pastebin.com/RXXhcwpN
TrustFrameworkExtensions.xml: https://pastebin.com/UUfVNaJ7
PasswordChange.xml : https://pastebin.com/sPjZjNYT
PasswordReset.xml : https://pastebin.com/CxE3pMH3
Your password change xml should have the base policy node reference the trust framework extensions policy, not the trust framework base policy, as per my sample.
Ultimately the issue is because trust framework base file only contains part of the login-noninteractive technical profile. It is completed in the extension file, hence you must reference a file from which all dependencies are satisfied.
Got the same problem, but different from the question author my issue wasn't mixing up user flows with custom policies.
I was 100% sure that the root cause was not misconfiguration of login-noninteractive nor IdentityExperienceFramework to ProxyIdentityExperienceFrameworkAppId wrong setup as I followed copybook style ms docs on it.
The error cause turns out to be that the property accessTokenAcceptedVersion on IdentityExperienceFramework's manifest file was set to 2 instead of null.
Setting it to null solves the issue.
Considerations
How come this property was set to 2? And why is this a problem
Most likely because I've created the app initially configured to
support Accounts in any organizational directory (Any Azure AD
directory - Multitenant) but changed my mind and updated it to
support Accounts in this organizational directory only (your-tenant
only - Single tenant) changing the manifest property to look like so:
"signInAudience": "AzureADMyOrg",
as the docs states:
If signInAudience is azureADandPersonalMicrosoftAccount, the value (of accessTokenAcceptedVersion) must be 2.
How do I spot that issue?
By automating the environment configuration of an auxiliary B2C tenant by using the https://b2ciefsetupapp.azurewebsites.net/ and comparing each of the app's configs.
If you've setup IdentityExperienceFramework and ProxyIdentityExperienceFrameworkAppId manually by following ms docs and still facing this obscure issue:
I highly recommend you delete your apps and rely on the automated tool. (I've learned my lesson)

AAD-FindLocalAccountWithSocialEmail produces No suitable claims providers were found

I am attempting to auto-link a social provider login to an existing local account.
I'm using the auto-linking sample, but doing so having replaced the sample Facebook ClaimsProviders/etc with ones for Google. Using my version of B2C_1A_ACCOUNTLINK_SUSI, I can create and login to a local account successfully. I can also add the user successfully with Google (if I delete the user beforehand - no linking). So, I know that these both work individually.
Yet, when I create the local account and attempt to login using Google as the idp and link that, I get an error that there are no suitable claims providers found.
Has anyone tried this auto-linking sample with Google and seen any tweaks I may need to Input/Output claims or other? I have made several over the course of a couple of weekends trying to get this to work. AppInsights shows that it happens in AAD-FindLocalAccountWithSocialEmail (AzureActiveDirectoryProvider) and Exception is: No suitable claims providers were found.
Any suggestions appreciated. I can post my Google ClaimsProvider blocks if that would help diagnose.
Probably because the IdP was not enabled using the issuers collection.
In all your Google IdP technical profiles, make sure to have:
<OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="google.com" AlwaysUseDefaultValue="true" />
In Google-OAUTH-Link technical profile, make sure to have this:
<Metadata>
<Item Key="ClaimTypeOnWhichToEnable">currentIssuers</Item>
<Item Key="ClaimValueOnWhichToEnable">google.com</Item>
</Metadata>
<!--snip-->
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateUserIdentityToLink" />
<OutputClaimsTransformation ReferenceId="AppendUserIdentityToLink" />
</OutputClaimsTransformations>
<EnabledForUserJourneys>OnItemExistenceInStringCollectionClaim</EnabledForUserJourneys>
In the HandleLinkLocalToSocial subjourney, make sure to add Google IdP in the linking steps:
<OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.idpselections">
<!--snip preconditions-->
<ClaimsProviderSelections DisplayOption="ShowSingleProvider">
<ClaimsProviderSelection TargetClaimsExchangeId="LinkGoogleExchange1"/>
</ClaimsProviderSelections>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<!--snip preconditions-->
<ClaimsExchanges>
<ClaimsExchange Id="LinkGoogleExchange1" TechnicalProfileReferenceId="Google-OAUTH-Link"/>
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="3" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
<!--snip preconditions-->
<ClaimsProviderSelections DisplayOption="ShowSingleProvider">
<ClaimsProviderSelection TargetClaimsExchangeId="LinkGoogleExchange2"/>
</OrchestrationStep>
<OrchestrationStep Order="4" Type="ClaimsExchange">
<!--snip preconditions-->
<ClaimsExchanges>
<ClaimsExchange Id="LinkGoogleExchange2" TechnicalProfileReferenceId="Google-OAUTH-Link"/>
</ClaimsExchanges>
</OrchestrationStep>
Had the same error using the auto-linking sample code. Check the output claims section of the technical profile AAD-FindLocalAccountWithSocialEmail. The claim currentUserIdentities is read from the directory there and is the input for the ExtractCurrentIssuers output claims transformation of this TP (which populates the currentIssuers claim and puts it into the claims bag).
The problem seems to be a wrong PartnerClaimType value.
Try
<OutputClaim ClaimTypeReferenceId="currentUserIdentities" PartnerClaimType="identities"/>
instead of
<OutputClaim ClaimTypeReferenceId="currentUserIdentities" PartnerClaimType="userIdentities"/>
If the PartnerClaimType is wrong the result is empty and an empty currentIssuers string collection leads to the error (in Google-OAUTH-Link TP) you described.
Would be nice if App-Insights could show the values of claims collections.
I have same problem relates to auto-account-linking sample, for my case I have only fix it by add output claim in AAD-FindLocalAccountWithSocialEmail Tehnical profile. Hopefully can help others
<OutputClaims>
<!-- sinp-->
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
</OutputClaims>
Issue is due to the hasPassword extension property. It doesn't exist by default for existing local B2C user profiles. Add true for that property to B2C user account, and it will start working. You will have to use Graph API to add value, so basically PATCH to user endpoint:
PATCH /v1.0/users/83bda93c-f782-431c-b969-12b5304c0668 HTTP/1.1
Host: graph.microsoft.com
Content-Type: application/json
Authorization: Bearer eyJ0...
{
"extension_83a963aa6ce74511923b85511f0f8dc7_haspassword" : true
}
See here for full discussion: https://bytemeta.vip/repo/azure-ad-b2c/samples/issues/430

AADB2C Embedded Password Reset: Local account discover is not being fired

We're implementing the embedded password reset, as is the new recommended practice. Once we click the Forgot your password? link the reset sub-journey is invoked as expected.
The reset sub-journy always skips the local account discovery step, where the user verifies their email to access their account information, and jumps directly to the screen to enter a new password - the new password entry then fails, because there is no account to write the new password into.
Our reset password journey is as follows:
<SubJourney Id="PasswordReset" Type="Call">
<OrchestrationSteps>
<OrchestrationStep Order="1" Type="ClaimsExchange">
<!-- This orchestration step never occurs. The user is never prompted for their email address. -->
<ClaimsExchanges>
<ClaimsExchange Id="PasswordResetUsingEmailAddressExchange" TechnicalProfileReferenceId="LocalAccountDiscoveryUsingEmailAddress" />
</ClaimsExchanges>
</OrchestrationStep>
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="NewCredentials" nicalProfileReferenceId="LocalAccountWritePasswordUsingObjectId" />
</ClaimsExchanges>
</OrchestrationStep>
</OrchestrationSteps>
</SubJourney>
Our code, so far, is lifted directly from the tutorials and sample code. How can we fix this issue, and has anyone else encountered the same problem?
Your question has been solved, post it as the answer to the end of the thread:
This is a bug in the B2C system - the initial combined sign in and
sign up step seems to set the email claim, even if the user hits the
"forgot your password" link. The
LocalAccountDiscoveryUsingEmailAddress profile attempts to use the
(blank) claim without prompting the user to enter an address, jumping
to the password write step but not picking up an account to write the
password to. We worked around this by creating a new resetEmail claim
used only by the account discovery and password write profiles.

How to disable storing of claims principal records in Azure B2C?

Azure B2C stores information about every claims principal logged in.
We do not need this information.
Users should be just passed through B2C from IdPs to service provider.
How to disable storing users information in B2C?
Here is the solution I found.
The base policy in SignIn userjourney
<UserJourney Id="SignIn">
contains an orchestration step that calls a technical profile AAD-UserWriteUsingAlternativeSecurityId
<OrchestrationStep Order="4" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="true">
<Value>objectId</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="AAD-UserWriteUsingAlternativeSecurityId" TechnicalProfileReferenceId="AAD-UserWriteUsingAlternativeSecurityId" />
</ClaimsExchanges>
</OrchestrationStep>
This step creates user registrations in Azure B2C.
If this user journey is overloaded in an extension policy and this step skipped there then users will not be created.
Another step that checks users registration with id AAD-UserReadUsingAlternativeSecurityId-NoError
TechnicalProfileReferenceId="AAD-UserReadUsingAlternativeSecurityId-NoError"
can also be skipped.
The authentication process with an external identity providers goes on flawlessly without these steps.
Azure AD B2C does not store anything if you are using an external identity provider. But if you are using Azure AD B2C's idedntity provider, It will store the claims within it.
Hope the information helps.

Resources