Azure AD B2C pre-populate a custom attribute in the SignUp policy - azure-ad-b2c

Does Azure AD B2C support pre-populating a custom attribute in the SignUp Policy when called from the Web application (ASP.Net MVC)?
We can create a custom SignUp attribute but we weren't able to find a specification in the documentation how to pass value to populate the custom attribute. If this is not supported out of the box, does anybody found a workaround?
Here are some more details for the context in case somebody has faced a similar scenario and found a useful solution:
We explore the options to solve the following scenario with Azure AD B2C: a registered user invites another person to signup to the application by sending an invitation email which has the url to the application’s login page along with a special invitation code(guid) as a query param, so it can click on the link and to be redirected to the Signup page. After the invited person creates an account, we need to use the code in order to associate the newly created user to the user who sent the invitation.
Currently this is implemented in the ASP.Net using the default identity provider (storing the user data in database with AspNet... tables). With replacing the local identity provider with the Azure AD B2C, we are loosing the context during the round-trip to the Azure AD B2C Signup page. The user clicks on the link on the email and gets to the SIgnUp page but the invitation code is not pre-populated.

A working sample of an invitation flow is here.
In the WingTipGamesWebApplication project, the InvitationController controller class has two action methods, Create and Redeem.
The Create action method sends a signed redemption link to the email address for the invited user. This redemption link contains this email address. It could also contain the invitation code.
The Redeem action method handles the redemption link. It passes the email address, as the verified_email claim in a JWT that is signed with the client secret of the Wingtip Games application (see the CreateSelfIssuedToken method in the Startup class in the WingTipGamesWebApplication project), from the redemption link to the Invitation policy. It could also pass the invitation code.
The Invitation policy can be found at here.
The Invitation policy declares the verified_email claim as an input claim:
<RelyingParty>
<DefaultUserJourney ReferenceId="Invitation" />
<TechnicalProfile Id="Invitation">
<InputTokenFormat>JWT</InputTokenFormat>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="WingTipGamesClientSecret" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_VerifiedEmail" />
</InputClaims>
</TechnicalProfile>
</RelyingParty>
The extension_verifiedEmail claim type, which is declared as a read-only field (so that it can't be modified by the end user), is mapped to the verified_email input claim:
<BuildingBlocks>
<ClaimsSchema>
<ClaimType Id="extension_VerifiedEmail">
<DisplayName>Verified Email</DisplayName>
<DataType>string</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OAuth2" PartnerClaimType="verified_email" />
<Protocol Name="OpenIdConnect" PartnerClaimType="verified_email" />
<Protocol Name="SAML2" PartnerClaimType="http://schemas.wingtipb2c.net/identity/claims/verifiedemail" />
</DefaultPartnerClaimTypes>
<UserInputType>Readonly</UserInputType>
</ClaimType>
</ClaimsSchema>
</BuildingBlocks>
The Invitation user journey can be found in here.
The second orchestration step of the Invitation user journey executes the LocalAccount-Registration-VerifiedEmail technical profile:
<UserJourney Id="Invitation">
<OrchestrationSteps>
...
<OrchestrationStep Order="2" Type="ClaimsExchange">
<ClaimsExchanges>
...
<ClaimsExchange Id="LocalAccountRegistrationExchange" TechnicalProfileReferenceId="LocalAccount-Registration-VerifiedEmail" />
</ClaimsExchanges>
</OrchestrationStep>
</OrchestrationSteps>
</UserJourney>
The LocalAccount-Registration-VerifiedEmail technical profile registers the local account with the verified email address:
<TechnicalProfile Id="LocalAccount-Registration-VerifiedEmail">
<DisplayName>WingTip Account</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.localaccount.registration</Item>
<Item Key="IpAddressClaimReferenceId">IpAddress</Item>
<Item Key="language.button_continue">Create</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CreateEmailFromVerifiedEmail" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_VerifiedEmail" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="extension_VerifiedEmail" Required="true" />
<OutputClaim ClaimTypeReferenceId="newPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="displayName" Required="true" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="localAccountAuthentication" />
<OutputClaim ClaimTypeReferenceId="executed-SelfAsserted-Input" DefaultValue="true" />
<OutputClaim ClaimTypeReferenceId="newUser" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="sub" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AzureActiveDirectoryStore-WriteUserByEmail-ThrowIfExists" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SSOSession-AzureActiveDirectory" />
</TechnicalProfile>
Before the local account is registered by the AzureActiveDirectoryStore-WriteUserByEmail-ThrowIfExists validation technical profile, the CreateEmailFromVerifiedEmail claims transformation copies the verified_email claim to the email claim:
<ClaimsTransformation Id="CreateEmailFromVerifiedEmail" TransformationMethod="FormatStringClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="extension_VerifiedEmail" TransformationClaimType="inputClaim" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringFormat" DataType="string" Value="{0}" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
To save the invitation code against the local account, you must:
Add the "extension_InvitationCode" claim to the claims schema
Add it as an input claim to the Invitation policy
Add it as an input claim to the LocalAccount-Registration-VerifiedEmail technical profile
Add it as a persisted claim to the AzureActiveDirectoryStore-WriteUserByEmail-ThrowIfExist technical profile

Related

Azure AD B2C - Forgot Password User Journey - Don't Allow old password?

I'm building an Azure AD B2C configuration based on custom policies. Sign in, profile edit, password change, etc. are already working as wanted.
But currently I'm struggling with the password forgot policy. I want to achieve that the new password does not equal to old one. Google and the Microsoft docs always give me examples for password changes. When I change the password, I have to enter the old one and the new one. Then I'm able to compare the old and the new one. For example like the way discribed here
But when a user has forgotten his password, then he is - of course - not able to enter the old password to compare it with the new one.
Is there any way to build a real password forgot policy without entering the old password but nevertheless ensure that the new password does not equal the old password?
Thanks in advance!
Alex
I was having the same problem and the answer from Jas Suri helped me a lot. However, I had some problems getting it to work. So, that's why I am sharing my final solution in case someone else is facing the same problem.
I have used the following as a base: https://github.com/azure-ad-b2c/samples/tree/master/policies/password-reset-not-last-password/policy but made some adjustments. Here is what I had to add to my existing policy to make it work:
ClaimSchema:
These claims will be used later.
<ClaimsSchema>
<ClaimType Id="SamePassword">
<DisplayName>samePassword</DisplayName>
<DataType>boolean</DataType>
<UserHelpText />
</ClaimType>
<ClaimType Id="resetPasswordObjectId">
<DisplayName>User's Object ID</DisplayName>
<DataType>string</DataType>
<DefaultPartnerClaimTypes>
<Protocol Name="OAuth2" PartnerClaimType="oid" />
<Protocol Name="OpenIdConnect" PartnerClaimType="oid" />
<Protocol Name="SAML2" PartnerClaimType="http://schemas.microsoft.com/identity/claims/objectidentifier" />
</DefaultPartnerClaimTypes>
<UserHelpText>Object identifier (ID) of the user object in Azure AD.</UserHelpText>
</ClaimType>
</ClaimsSchema>
ClaimsTransformation:
The first claim transformation checks whether resetPasswordObjectId is set, if not the previous attempted login with the new password apparently didn't work and thus the newPassword is not the same as the old / current password. The second claim transformation checks whether the claim SamePassword is equal to false. If not it throws an error and 'You can't use an old password' is displayed. More information on this later.
<ClaimsTransformations>
<ClaimsTransformation Id="CheckPasswordEquivalence" TransformationMethod="DoesClaimExist">
<InputClaims>
<InputClaim ClaimTypeReferenceId="resetPasswordObjectId" TransformationClaimType="inputClaim" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="SamePassword" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
<ClaimsTransformation Id="AssertSamePasswordIsFalse" TransformationMethod="AssertBooleanClaimIsEqualToValue">
<InputClaims>
<InputClaim ClaimTypeReferenceId="SamePassword" TransformationClaimType="inputClaim" />
</InputClaims>
<InputParameters>
<InputParameter Id="valueToCompareTo" DataType="boolean" Value="false" />
</InputParameters>
</ClaimsTransformation>
</ClaimsTransformations>
ClaimsProvider:
The overall idea is to use the newly entered password and attempt to login the user. In case the login is successful, the newPassword is the same as the old / current password and is not allowed. When logging in, we create an output claim and call it resetPasswordObjectId which is either unset or equal to the object id of the logged in user. We then check whether the resetPasswordObjectId exists (done in the claims transformation part), if not the newPassword can be used as it is not the same as the old / current password.
To display the correct error message in case the user entered the old password, we need to override UserMessageIfClaimsTransformationBooleanValueIsNotEqual in the TrustFrameworkLocalization.xml in the <LocalizedResources Id="api.localaccountpasswordreset.en"> like this <LocalizedString ElementType="ErrorMessage" StringId="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">You must not use your old password.</LocalizedString>.
When using the claim provider below, ensure to replace all parts marked with a TODO.
<ClaimsProviders>
<ClaimsProvider>
<DisplayName>Password Reset without same password</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="login-NonInteractive-PasswordChange">
<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/</Item>
<!-- TODO replace YOUR-TENANT-ID -->
<Item Key="METADATA">https://login.microsoftonline.com/YOUR-TENANT.onmicrosoft.com/.well-known/openid-configuration</Item>
<!-- TODO replace YOUR-TENANT-ID -->
<Item Key="authorization_endpoint">https://login.microsoftonline.com/YOUR-TENANT.onmicrosoft.com/oauth2/token</Item>
<Item Key="response_types">id_token</Item>
<Item Key="response_mode">query</Item>
<Item Key="scope">email openid</Item>
<!-- TODO ensure this line is commented out-->
<!-- <Item Key="grant_type">password</Item> -->
<Item Key="UsePolicyInRedirectUri">false</Item>
<Item Key="HttpBinding">POST</Item>
<!-- TODO -->
<!-- ProxyIdentityExperienceFramework application / client id -->
<Item Key="client_id">YOUR-PROXY-CLIENT-ID</Item>
<!-- Native App -->
<!-- TODO -->
<!-- IdentityExperienceFramework application / client id -->
<Item Key="IdTokenAudience">YOUR-IDENTITY-CLIENT-ID</Item>
<!-- Web Api -->
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="username" Required="true" />
<!-- INFO: replaced oldPassword with newPassword, that way we try logging in with the new password. If the login is successful, we know the newPassword is the same as the old / current password-->
<InputClaim ClaimTypeReferenceId="newPassword" PartnerClaimType="password" Required="true" />
<InputClaim ClaimTypeReferenceId="grant_type" DefaultValue="password" />
<InputClaim ClaimTypeReferenceId="scope" DefaultValue="openid" />
<InputClaim ClaimTypeReferenceId="nca" PartnerClaimType="nca" DefaultValue="1" />
<!-- TODO -->
<!-- ProxyIdentityExperienceFramework application / client id -->
<InputClaim ClaimTypeReferenceId="client_id" DefaultValue="YOUR-PROXY-CLIENT-ID" />
<!-- TODO -->
<!-- IdentityExperienceFramework application / client id -->
<InputClaim ClaimTypeReferenceId="resource_id" PartnerClaimType="resource" DefaultValue="YOUR-IDENTITY-CLIENT-ID" />
</InputClaims>
<OutputClaims>
<!-- INFO: assign the objectId (oid) to resetPasswordObjectId, since the claim objectId might already be set. In that case there would be no way of knowing whether it was set due to the attempted login with the newPassword-->
<OutputClaim ClaimTypeReferenceId="resetPasswordObjectId" 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>
<!--Logic to check new password is not the same as old password
Validates old password before writing new password-->
<TechnicalProfile Id="LocalAccountWritePasswordUsingObjectId">
<DisplayName>Reset password</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>
<!-- set in the TrustFrameworkLocalization.xml -->
<!-- <Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">You must not use your old password.</Item> -->
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="newPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="login-NonInteractive-PasswordChange" ContinueOnError="true" />
<ValidationTechnicalProfile ReferenceId="ComparePasswords" />
<ValidationTechnicalProfile ReferenceId="AAD-UserWritePasswordUsingObjectId">
<Preconditions>
<Precondition Type="ClaimEquals" ExecuteActionsIf="true">
<Value>SamePassword</Value>
<Value>True</Value>
<Action>SkipThisValidationTechnicalProfile</Action>
</Precondition>
</Preconditions>
</ValidationTechnicalProfile>
</ValidationTechnicalProfiles>
</TechnicalProfile>
<!-- Runs claimsTransformations to make sure new and old passwords differ -->
<TechnicalProfile Id="ComparePasswords">
<DisplayName>Compare Email And Verify Email</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="CheckPasswordEquivalence" />
<OutputClaimsTransformation ReferenceId="AssertSamePasswordIsFalse" />
</OutputClaimsTransformations>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
You can do it with some logic with Validation Technical profiles:
Call login-noninteractive with continueOnError=true
Call a claimTransform to generate a boolean if a claim (like objectId) is null
Use the boolean for the proceeding logic, lets call it pwdIsLastPwd
Call a claimTransform to assert pwdIsLastPwd = false
If it is true, throw an error - "you cannot use this password" using the claimTransform error handler
Continue with the rest of reset password flow
References:
https://learn.microsoft.com/en-us/azure/active-directory-b2c/validation-technical-profile#validationtechnicalprofiles
Call Claim transform from VTP, Boolean ClaimTransform check if claim exists
Assert boolean is true/false
"The UserMessageIfClaimsTransformationBooleanValueIsNotEqual self-asserted technical profile metadata controls the error message that the technical profile presents to the user. "

Any way to provide default values for inputs in an Azure AD B2C custom policy?

I am working on integrating Azure AD B2C into an existing shopping cart application, replacing an existing user identity solution.
I have already created a custom policy to implement user registration / sign-up and integrated it into the normal account creation process. However, I am having a problem with integrating registration during the checkout process.
With the old IDP, the checkout process first collects the user's name and email address. After that has been collected, the user is given the option to create an account with that information (if they are not already logged in). In order to avoid confusing double-entry of the email address, I would like to pass the email address that the user already entered to the B2C sign-up policy and have it fill in the email address input on the form.
Is there any way to do this? I don't find anything like this being addressed in the B2C documentation.
You can use a sign up policy with a claims resolver. Send the email in the query parameter. It’ll prepopulate the email into the text box.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/claim-resolver-overview
Based on the documentation linked by #JasSuri, I was able to come up with a solution.
To implement this, you modify the sign-up technical profile. You must add three things:
Add item IncludeClaimResolvingInClaimsHandling with value true to the metadata
Add a DefaultValue attribute to the email input claim with an appropriate claims resolver notation as the value
Add an AlwaysUseDefaultValue attribute to the email input claim with true as the value
I used an Oauth2 key-value claims resolver (which supports arbitrary query string parameters) and a query parameter named register_email.
The resulting technical profile looks like this:
<TechnicalProfile Id="LocalAccountSignUpWithLogonEmail">
<DisplayName>Email signup</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="IpAddressClaimReferenceId">IpAddress</Item>
<Item Key="ContentDefinitionReferenceId">api.localaccountsignup</Item>
<Item Key="language.button_continue">Create</Item>
<Item Key="IncludeClaimResolvingInClaimsHandling">true</Item> <!-- ADD THIS -->
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaims>
<!-- ADD DefaultValue AND AlwaysUseDefaultValue ATTRIBUTES BELOW -->
<InputClaim ClaimTypeReferenceId="email"
DefaultValue="{OAUTH-KV:register_email}"
AlwaysUseDefaultValue="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
<OutputClaim ClaimTypeReferenceId="newPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
<OutputClaim ClaimTypeReferenceId="executed-SelfAsserted-Input" DefaultValue="true" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
<OutputClaim ClaimTypeReferenceId="newUser" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonEmail" />
</ValidationTechnicalProfiles>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
</TechnicalProfile>

Rest API call in custom policy in Azure AD B2C

I am new and have tried to build custom policy for sign-in by invoking a REST API call to legacy.
The API expects below parameters as input:
client_id=fixed value, client_secret=fixed value, grant_type=fixed value, scope=fixed value, username=variable, password=variable
The above parameters are not for Azure valued but some fixed values that need to be sent in the request.
I have added few portions in the extensions custom policy but seems the request body is not forming correctly and I am getting the error The claims exchange User Migration Via Legacy specified in step '1' returned HTTP error response with Code Bad Request and Reason 'Bad Request'.
What am I doing wrong here? Please help.
Added my portion :
<BuildingBlocks>
<ClaimsSchema>
<ClaimType Id="RequestBody">
<DisplayName>Request body</DisplayName>
<DataType>string</DataType>
<UserHelpText>RequestBody</UserHelpText>
</ClaimType>
</ClaimsSchema>
<ClaimsTransformations>
<ClaimsTransformation Id="GenerateRequestBody" TransformationMethod="GenerateJson">
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" TransformationClaimType="username" />
<InputClaim ClaimTypeReferenceId="password" TransformationClaimType="password" />
</InputClaims>
<InputParameters>
<InputParameter Id="client_id" DataType="string" Value="client" />
<InputParameter Id="client_secret" DataType="string" Value="sec" />
<InputParameter Id="grant_type" DataType="string" Value="grant" />
<InputParameter Id="scope" DataType="string" Value="scope" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="RequestBody" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
</ClaimsTransformations>
<ClaimsProviders>
<ClaimsProvider>
<DisplayName>REST API to communicate with Legacy IdP</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="UserMigrationViaLegacyIdp">
<DisplayName>REST API call to communicate with Legacy IdP</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://</Item>
<Item Key="AuthenticationType">None</Item>
<Item Key="SendClaimsIn">Body</Item>
<Item Key="AllowInsecureAuthInProduction">true</Item>
<Item Key="ClaimUsedForRequestPayload">RequestBody</Item>
</Metadata>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="GenerateRequestBody" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="RequestBody" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="tokenSuccess" DefaultValue="false" />
<OutputClaim ClaimTypeReferenceId="migrationRequired" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
Can you share the actual JSON created by the claim transformation? Is it well formatted? You can make troubleshooting easy by displaying the output of <InputClaimsTransformation ReferenceId="GenerateRequestBody" /> before you actually use it as input claim inside the REST API technical profile. This will reveal what's going on with your JSON format.
To do that, first create a self asserted technical profile and add <InputClaimsTransformation ReferenceId="GenerateRequestBody" /> as an output claim transformation to it. Your output claim should be the claim which you will use later in next step in the journey.
In the user journey, add a step that calls the self asserted technical profile. Add next step with App Insights to write the output claim to App Insights( https://learn.microsoft.com/en-us/azure/active-directory-b2c/analytics-with-application-insights)
This will document claim value that REST API is receiving.

Can you pass password claims between steps in Azure AD B2C Custom Policies?

Can you pass password claims between steps in Azure AD B2C Custom Policies?
My symptom is that after signing up using a multiple page custom policy, a user cannot sign in until they reset their password.
I am asking this as I spent many hours debugging a problem that it turns out could not be fixed. I found the answer under another question (Azure AD B2C Multi steps custom policy) that was differently worded but had similar symptoms.
I am posting here in the hope it is more easily found and helpful to others. Apologies if you think this is a duplicate.
In short, no. See Azure AD B2C Multi steps custom policy.
A password claim is "scoped" to a given step. This means the orchestration step that collects the password claim from the end user must be the same step that writes it to the User object.
I was looking for the same and have found a way to do multi step sign up. I tried several methods, including a outputclaimstransformation to store the password in another claim, but that did not work out. Because I already needed to do some input validation against an API I found a way to also copy the password to a new claim (plaintextPassword) that is not scoped to one orchestration step. This claim can be used in a later step to create the user account with the password provided by the plaintextPassword claim.
Create a self asserted technical profile that has an inputclaim (the password) and a validation technical profile. In the validation technical profile you can copy the password to a claim of type string with an inputclaimstransformation. Then add the new claim as outputclaim to the validation profile and to the technical profile. See code below for an example:
<ClaimType Id="plaintextPassword">
<DisplayName>password</DisplayName>
<DataType>string</DataType>
<UserInputType>TextBox</UserInputType>
</ClaimType>
<ClaimType Id="password">
<DisplayName>Your password</DisplayName>
<DataType>string</DataType>
<UserHelpText>Your password</UserHelpText>
<UserInputType>Password</UserInputType>
</ClaimType>
<ClaimsTransformation Id="CopyPassword" TransformationMethod="FormatStringClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="password" TransformationClaimType="inputClaim" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringFormat" DataType="string" Value="{0}" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="plaintextPassword" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
<TechnicalProfile Id="SignUp-PasswordValidation">
<DisplayName>Email signup</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="ServiceUrl">url</Item>
<Item Key="AuthenticationType">ClientCertificate</Item>
<Item Key="SendClaimsIn">Body</Item>
</Metadata>
<CryptographicKeys>
<Key Id="ClientCertificate" StorageReferenceId="B2C_1A_ClientCertificate" />
</CryptographicKeys>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CopyPassword" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="claim_to_validate" PartnerClaimType="claim_to_validate" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="plaintextPassword" />
</OutputClaims>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>
<TechnicalProfile Id="SignUp">
<DisplayName>Email signup</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Metadata>
<Item Key="IpAddressClaimReferenceId">IpAddress</Item>
<Item Key="ContentDefinitionReferenceId">api.localaccountsignup</Item>
<Item Key="setting.retryLimit">3</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" Required="true" />
<OutputClaim ClaimTypeReferenceId="claim_to_validate" Required="true" />
<OutputClaim ClaimTypeReferenceId="password" Required="true" />
<OutputClaim ClaimTypeReferenceId="executed-SelfAsserted-Input" DefaultValue="true" />
<OutputClaim ClaimTypeReferenceId="plaintextPassword" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="SignUp-Validation" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
In the technical profile where you create the user in AAD add this line:
<PersistedClaim ClaimTypeReferenceId="plaintextPassword" PartnerClaimType="password"/>
I was able to solve the problem. When we have to add the user information (LocalAccountSignUpWithLogonEmail) I removed the validation that called the technical profile that is written in AAD, I changed it to a validation that calls another technical profile. This will copy our password through a ClaimsTransformation that will save our password in another InputClaim to be visible in any other step of our flow.
<ClaimType Id="plaintextPassword">
<DisplayName>password</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ClaimType Id="passwordTransformation">
<DisplayName>requestSocialRestSecondCall</DisplayName>
<DataType>string</DataType>
</ClaimType>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="TransformationPassword" />
</ValidationTechnicalProfiles>
<ClaimsProvider> <!-- Copy Password-->
<DisplayName>Copy Password</DisplayName>
<TechnicalProfiles>
<TechnicalProfile Id="TransformationPassword">
<DisplayName>Copy Pass</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<InputClaims>
<InputClaim ClaimTypeReferenceId="passwordTransformation" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="passwordTransformation" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="Generate_TranformationPassword" />
</OutputClaimsTransformations>
</TechnicalProfile>
</TechnicalProfiles>
</ClaimsProvider>
<ClaimsTransformation Id="Generate_TranformationPassword" TransformationMethod="CopyClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="newPassword" TransformationClaimType="inputClaim" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="plaintextPassword" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
I removed the validation that called the technical profile that is written in AAD and changed it to a validation that calls another technical profile.
This will copy the password through a ClaimsTransformation that will save our password in another InputClaim to be visible in any other step of our flow.
<TechnicalProfile Id="SignUp-PasswordValidation">
<Metadata>
<Item Key="ServiceUrl">url</Item>
<Item Key="AuthenticationType">ClientCertificate</Item>
<Item Key="SendClaimsIn">Body</Item>
</Metadata>
What is the url you used?
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
What was you application to create the Secret?

Azure AD B2C Password Reset policy without email verification step

Is it possible to create custom policy to reset password for already known email?
I create user using Graph API and send invitation email to the specified email
address.
I want user to click on the link in that email and just set password for his account.
I can create signed token with this email claim and send as assertion to my custom policy. So policy gets email as input claim. I see it in the trace.
But I am not able to bypass email verification step in the password reset journey - when I remove it, I get 500 server error without additional detail.
I tried to send objectId for the user as input claim as well, but it does not help either.
Is there a way to skip email verification?
You have the following options that vary the user experience:
Display the email address as a read-only field and remove the email verification requirement.
Remove the email verification step.
Display the email address as a read-only field
1) Create a readOnlyEmail claim type:
<ClaimType Id="readOnlyEmail">
<DisplayName>Email Address</DisplayName>
<DataType>string</DataType>
<UserInputType>Readonly</UserInputType>
</ClaimType>
2) Create a claims transformation that copies from the email claim to the readOnlyEmail claim:
<ClaimsTransformation Id="CopyFromEmailToReadOnlyEmail" TransformationMethod="FormatStringClaim">
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" TransformationClaimType="inputClaim" />
</InputClaims>
<InputParameters>
<InputParameter Id="stringFormat" DataType="string" Value="{0}" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="readOnlyEmail" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
3) Add the CopyFromEmailToReadOnlyEmail claims transformation as an input claims transformation to the LocalAccountDiscoveryUsingEmailAddress technical profile and then replace the email claim type with readOnlyemail as the input and output claims for this technical profile:
<TechnicalProfile Id="LocalAccountDiscoveryUsingEmailAddress">
<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="IpAddressClaimReferenceId">IpAddress</Item>
<Item Key="ContentDefinitionReferenceId">api.localaccountpasswordreset</Item>
</Metadata>
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
</CryptographicKeys>
<IncludeInSso>false</IncludeInSso>
<InputClaimsTransformations>
<InputClaimsTransformation ReferenceId="CopyFromEmailToReadOnlyEmail" />
</InputClaimsTransformations>
<InputClaims>
<InputClaim ClaimTypeReferenceId="readOnlyEmail" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="readOnlyEmail" Required="true" />
<OutputClaim ClaimTypeReferenceId="objectId" />
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" />
</OutputClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingEmailAddress" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
Remove the email verification step
1) Change the first step for the PasswordReset journey from:
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="PasswordResetUsingEmailAddressExchange" TechnicalProfileReferenceId="LocalAccountDiscoveryUsingEmailAddress" />
</ClaimsExchanges>
</OrchestrationStep>
to:
<OrchestrationStep Order="1" Type="ClaimsExchange">
<ClaimsExchanges>
<ClaimsExchange Id="UserReadUsingEmailAddressExchange" TechnicalProfileReferenceId="AAD-UserReadUsingEmailAddress" />
</ClaimsExchanges>
</OrchestrationStep>

Resources