- by gowthamk91
Introduction:
When working with Azure AD B2C, there are scenarios where users must reset their password upon their first sign-in, especially when passwords are either admin reset or auto-generated during user migration. In this article, we’ll learn how to configure a Custom Policy in Azure AD B2C that enforces a password reset on the first login using custom orchestration and technical profiles.
Before proceeding, make sure you’ve reviewed my foundational article:
Get Started with Azure ADB2C Custom Policies – Gowtham K
Why Force Password Reset on First Login?
Forcing users to change their password on their first login is a key security step in two common cases:
- Admin resets the password for a user (e.g., recovery or security rotation).
- Users are migrated into Azure AD B2C with a temporary or random password.
In our scenario, we performed user migration using Microsoft Graph API and set the forceChangePasswordNextSignIn property to true, which ensures that the user must change their password upon the first login.
The user will get the following error message on the screen when they try to log in

Now, let’s build a custom policy, which redirects the user to the password reset flow when the user’s password is reset by an admin or the user is migrated to the B2C directory with the password profile forceChangePasswordNextSignIn set to true.
User Migration with Microsoft Graph API
When migrating users into Azure AD B2C, you can use the Microsoft Graph API endpoint:
POST https://graph.microsoft.com/v1.0/users
Body
{
"displayName": "Gowtham Kumar",
"identities": [
{
"signInType": "emailAddress",
"issuer": "gowthamcbe.onmicrosoft.com",
"issuerAssignedId": "gowthamkk7@gmail.com"
}
],
"passwordProfile": {
"password": "Password456!!",
"forceChangePasswordNextSignIn": true
},
"passwordPolicies": "DisablePasswordExpiration"
}
Note: Ensure forceChangePasswordNextSignIn is set to true. This property flags the account for a password change during the next sign-in attempt.
Custom Policy Configuration:
Once the user is created with the forced password reset flag, we’ll extend our Azure AD B2C custom policy to handle the password change during the login journey.
Open TRUSTFRAMEWORKESTENSIONS.xml file or create a new extension file. In my case, for demo purposes, I create a new extension file.
Step 1: Add Custom Claim Types
<ClaimType Id="forceChangePasswordNextLogin">
<DisplayName>forceChangePasswordNextLogin</DisplayName>
<DataType>boolean</DataType>
<AdminHelpText>Directory property, Whether the user password has expired</AdminHelpText>
</ClaimType>
<ClaimType Id="continueOnPasswordExpiration">
<DisplayName>continueOnPasswordExpiration</DisplayName>
<DataType>boolean</DataType>
<AdminHelpText>continue ests non-interactive upon password expiration</AdminHelpText>
</ClaimType>
<ClaimType Id="samePassword">
<DisplayName>samePassword</DisplayName>
<DataType>boolean</DataType>
<AdminHelpText>Whether user enters the same password</AdminHelpText>
</ClaimType>
<ClaimType Id="userMsg">
<DisplayName></DisplayName>
<DataType>string</DataType>
<AdminHelpText>A claim responsible for holding user messages</AdminHelpText>
<UserInputType>Paragraph</UserInputType>
</ClaimType>These claims are essential for tracking password state, comparison, and communicating messages to the user during the password change process.
Step 2: Add Claim Transformations
We’ll now define the logic to ensure users don’t reuse their old passwords.
<ClaimsTransformations>
<!--Compare the old and new password-->
<ClaimsTransformation Id="CompareOldAndNewPassword" TransformationMethod="CompareClaims">
<InputClaims>
<InputClaim ClaimTypeReferenceId="password" TransformationClaimType="inputClaim1" />
<InputClaim ClaimTypeReferenceId="newPassword" TransformationClaimType="inputClaim2" />
</InputClaims>
<InputParameters>
<InputParameter Id="operator" DataType="string" Value="EQUAL" />
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="samePassword" TransformationClaimType="outputClaim" />
</OutputClaims>
</ClaimsTransformation>
<!--Assert whether the old and new passwords are same, and return the UserMessageIfClaimsTransformationBooleanValueIsNotEqual error message-->
<ClaimsTransformation Id="ThrowErrorWhenPassowrdIsSame" TransformationMethod="AssertBooleanClaimIsEqualToValue">
<InputClaims>
<InputClaim ClaimTypeReferenceId="samePassword" TransformationClaimType="inputClaim" />
</InputClaims>
<InputParameters>
<InputParameter Id="valueToCompareTo" DataType="boolean" Value="false" />
</InputParameters>
</ClaimsTransformation>
</ClaimsTransformations>This transformation compares the old and new passwords and throws an error if the user tries to reuse the same password.
Step 3: Modify Technical Profiles
a) Update Sign-In Technical Profiles
In SelfAsserted-LocalAccountSignin-Email, include the following output claim:
<OutputClaim ClaimTypeReferenceId="forceChangePasswordNextLogin" />In login-NonInteractive, add the input and output claims:
<InputClaim ClaimTypeReferenceId="continueOnPasswordExpiration" DefaultValue="true" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="forceChangePasswordNextLogin" PartnerClaimType="passwordExpired" />
</OutputClaims>
This enables Azure AD B2C to check whether the user’s password has expired or needs to be reset.
b) Add a Technical Profile to Assert Password Change
<TechnicalProfile Id="ThrowErrorWhenPassowrdIsSame">
<DisplayName>Assert New Password is Different</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.ClaimsTransformationProtocolProvider, Web.TPEngine" />
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="samePassword" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CompareOldAndNewPassword" />
<OutputClaimsTransformation ReferenceId="ThrowErrorWhenPassowrdIsSame" />
</OutputClaimsTransformations>
</TechnicalProfile>
c) Add the Password Reset Screen for Expired Passwords
<TechnicalProfile Id="SelfAsserted-ForcePasswordReset-ExpiredPassword">
<DisplayName>Password Expired</DisplayName>
<Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine" />
<Metadata>
<Item Key="ContentDefinitionReferenceId">api.selfasserted</Item>
<Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Please enter a different password</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="userMsg" DefaultValue="Your password has expired, please change to a new password." />
</InputClaims>
<DisplayClaims>
<DisplayClaim ClaimTypeReferenceId="userMsg" />
<DisplayClaim ClaimTypeReferenceId="password" Required="true" />
<DisplayClaim ClaimTypeReferenceId="newPassword" Required="true" />
<DisplayClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
</DisplayClaims>
<ValidationTechnicalProfiles>
<ValidationTechnicalProfile ReferenceId="login-NonInteractive" />
<ValidationTechnicalProfile ReferenceId="ThrowErrorWhenPassowrdIsSame" />
<ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingSignInName" />
<ValidationTechnicalProfile ReferenceId="AAD-UserWritePasswordUsingObjectId-ResetNextLogin" />
</ValidationTechnicalProfiles>
</TechnicalProfile>
This technical profile displays the “password expired” screen, validates the old password, ensures the new password is unique, and persists it in Azure AD B2C.
d) Supporting Technical Profiles
<TechnicalProfile Id="AAD-UserReadUsingSignInName">
<Metadata>
<Item Key="Operation">Read</Item>
</Metadata>
<InputClaims>
<InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="signInNames.emailAddress" Required="true" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="objectId" />
</OutputClaims>
<IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>
<TechnicalProfile Id="AAD-UserWritePasswordUsingObjectId-ResetNextLogin">
<PersistedClaims>
<PersistedClaim ClaimTypeReferenceId="forceChangePasswordNextLogin" PartnerClaimType="passwordProfile.forceChangePasswordNextSignIn" DefaultValue="false" AlwaysUseDefaultValue="true" />
</PersistedClaims>
<IncludeTechnicalProfile ReferenceId="AAD-UserWritePasswordUsingObjectId" />
</TechnicalProfile>
Step 4: Update the User Journey
Add a new orchestration step that triggers the password reset screen if the password has expired.
<OrchestrationStep Order="3" Type="ClaimsExchange">
<Preconditions>
<Precondition Type="ClaimsExist" ExecuteActionsIf="false">
<Value>forceChangePasswordNextLogin</Value>
<Action>SkipThisOrchestrationStep</Action>
</Precondition>
</Preconditions>
<ClaimsExchanges>
<ClaimsExchange Id="ForcePasswordResetUponPasswordExpiration" TechnicalProfileReferenceId="SelfAsserted-ForcePasswordReset-ExpiredPassword" />
</ClaimsExchanges>
</OrchestrationStep>
The order number may vary based on your Sign In/Sign Up User flow.
Step 5: Add the Claim to the Sign-In Policy
Finally, in your SignIn policy, include the following output claim:
<OutputClaim ClaimTypeReferenceId="forceChangePasswordNextLogin" PartnerClaimType="passwordExpired" DefaultValue="false" />User Flow
- When a user signs in, the policy checks the
forceChangePasswordNextSignInflag. - If true, the user is redirected to the password reset screen (
SelfAsserted-ForcePasswordReset-ExpiredPassword).
- The system ensures the new password differs from the old one.
- The password is updated in Azure AD B2C, and the flag is reset to false.
- The user proceeds to access the application successfully with the new password.
Summary
Through this article, we learned how to force Azure AD B2C users to reset their passwords during their first login using custom policies.
By leveraging the forceChangePasswordNextSignIn property in Microsoft Graph and integrating custom orchestration logic in B2C, you can enhance your tenant’s security posture and user experience during password migrations or admin resets.
I hope you found this article helpful!
Share your feedback and thoughts in the comments — I’d love to hear how you’ve implemented similar policies in your environment.
Source Code – GitHub