2
votes

I am trying to use https://github.com/azure-ad-b2c/samples/blob/master/policies/force-password-reset-first-logon to implement the Password reset on initial login for local accounts. I have followed all the steps in https://docs.microsoft.com/en-us/azure/active-directory-b2c/custom-policy-get-started except creating a Facebook secret key since I need to sign in using only local accounts. While testing the policy, when I sign-in using my email address, I am able to successfully login to my application instead of getting re-directed to the password reset page and the custom attribute is not cleared.

I am using Graph API to set the custom attribute "Extenstion_000000000000000000000000000000000_mustResetPassword" with user creation and ForceChangePasswordNextSignIn property is set to false. Can you please direct me where I am doing this wrong?

I would like the user to sign in for the first time and get redirected to reset the password and clear the custom attribute. Please Help!

I am creating user using Graph API

 var result = await graphClient.Users
                    .Request()
                    .AddAsync(new User
                    {
                        GivenName = user.FirstName,
                        Surname = user.LastName,
                        DisplayName = user.UserName,
                        Identities = new List<ObjectIdentity>
                        {
                        new ObjectIdentity()
                        {
                            SignInType = SignInType.emailAddress.ToDescription(),
                            Issuer = config.TenantId,
                            IssuerAssignedId = user.Email
                        }
                        },
                        PasswordProfile = new PasswordProfile()
                        {
                            Password = password,
                            ForceChangePasswordNextSignIn =false
                        },
                        PasswordPolicies = "DisablePasswordExpiration",
                        AdditionalData = extensionInstance
                    }); 


**TrustFrameworkExtensions.xml code**

<?xml version="1.0" encoding="utf-8" ?>

    <TrustFrameworkPolicy 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
      xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
      xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06" 
      PolicySchemaVersion="0.3.0.0" 
      TenantId="tenantId.onmicrosoft.com" 
      PolicyId="B2C_1A_TrustFrameworkExtensions" 
      PublicPolicyUri="http://tenantId.onmicrosoft.com/B2C_1A_TrustFrameworkExtensions">
      
      <BasePolicy>
        <TenantId>tenantId.onmicrosoft.com</TenantId>
        <PolicyId>B2C_1A_TrustFrameworkBase</PolicyId>
      </BasePolicy>
      <BuildingBlocks>
      <ClaimsSchema>
        <!--Demo: Specifies whether user must reset the password-->
        <ClaimType Id="extension_mustResetPassword">
            <DisplayName>Must reset password</DisplayName>
            <DataType>boolean</DataType>
            <UserHelpText>Specifies whether user must reset the password</UserHelpText>
          </ClaimType>
          
      </ClaimsSchema>
      </BuildingBlocks>
    
      <ClaimsProviders>
    
    
        <ClaimsProvider>
          <DisplayName>Local Account SignIn</DisplayName>
          <TechnicalProfiles>
             <TechnicalProfile Id="login-NonInteractive">
              <Metadata>
                 <Item Key="client_id">00000000-0000-0000-0000-000000000000</Item>
                <Item Key="IdTokenAudience">00000000-0000-0000-0000-000000000000</Item>
              </Metadata>
              <InputClaims>
                <InputClaim ClaimTypeReferenceId="client_id" DefaultValue="00000000-0000-0000-0000-000000000000" />
                <InputClaim ClaimTypeReferenceId="resource_id" PartnerClaimType="resource" DefaultValue="00000000-0000-0000-0000-000000000000" />
              </InputClaims>
            </TechnicalProfile>
          </TechnicalProfiles>
        </ClaimsProvider>
     <ClaimsProvider>
          <DisplayName>Azure Active Directory</DisplayName>
          <TechnicalProfiles>  
            <TechnicalProfile Id="AAD-Common">
              <DisplayName>Azure Active Directory</DisplayName>
              <!--  Demo action required: Provide objectId and appId before using extension properties.
                    For more information: https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-create-custom-attributes-profile-edit-custom 
                    Action required: Insert objectId and appId here -->
              <Metadata>
                <Item Key="ApplicationObjectId">00000000-0000-0000-0000-000000000000</Item>
                <Item Key="ClientId">00000000-0000-0000-0000-000000000000</Item>
              </Metadata>
            </TechnicalProfile>
    
            <TechnicalProfile Id="AAD-UserReadUsingObjectId">
              <OutputClaims>
                <!--Demo: Read the 'must reset password' extension attribute -->
                <OutputClaim ClaimTypeReferenceId="extension_mustResetPassword" />
              </OutputClaims>
            </TechnicalProfile>
    
            <TechnicalProfile Id="AAD-UserRemoveMustResetPasswordUsingObjectId">
              <Metadata>
                <Item Key="Operation">DeleteClaims</Item>
              </Metadata>
              <InputClaims>
                <InputClaim ClaimTypeReferenceId="objectId" Required="true" />
              </InputClaims>
              <PersistedClaims>
                <PersistedClaim ClaimTypeReferenceId="objectId" />
                <PersistedClaim ClaimTypeReferenceId="extension_mustResetPassword" />            
              </PersistedClaims>
              <IncludeTechnicalProfile ReferenceId="AAD-Common" />
            </TechnicalProfile>
    
            <!--Demo: to create the extension attribute extension_mustResetPassword, you should upload the policy 
                and create one account. Then ***comment out this technical profile***.
                -->
            <TechnicalProfile Id="AAD-UserWriteUsingLogonEmail">
              <PersistedClaims>
                <PersistedClaim ClaimTypeReferenceId="extension_mustResetPassword" DefaultValue="true" />
              </PersistedClaims>
            </TechnicalProfile>
          </TechnicalProfiles>
        </ClaimsProvider>
    
      </ClaimsProviders>
    
        <UserJourneys>
           <UserJourney Id="SignUpOrSignInWithForcePasswordReset">
          <OrchestrationSteps>
          
            <OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
              <ClaimsProviderSelections>
                
               <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="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail" />
              </ClaimsExchanges>
            </OrchestrationStep>
    
            <!-- This step reads any user attributes that we may not have received when authenticating using ESTS so they can be sent 
              in the token. -->
            <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="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
              </ClaimsExchanges>
            </OrchestrationStep>
    
            <!--Demo: check if change password is required. If yes, ask the user to reset the password-->
            <OrchestrationStep Order="4" Type="ClaimsExchange">
              <Preconditions>
    
                <Precondition Type="ClaimsExist" ExecuteActionsIf="false">
                  <Value>extension_mustResetPassword</Value>
                  <Action>SkipThisOrchestrationStep</Action>
                </Precondition>            
                <Precondition Type="ClaimEquals" ExecuteActionsIf="false">
                  <Value>extension_mustResetPassword</Value>
                  <Value>True</Value>
                  <Action>SkipThisOrchestrationStep</Action>
                </Precondition>            
              </Preconditions>        
              <ClaimsExchanges>
                <ClaimsExchange Id="NewCredentials" TechnicalProfileReferenceId="LocalAccountWritePasswordUsingObjectId" />
              </ClaimsExchanges>
            </OrchestrationStep>
    
              <!--Demo: check if change password is required. If yes remove the value of the extension attribute. 
                  So, on the next time user dons' t need to update the password-->
            <OrchestrationStep Order="5" Type="ClaimsExchange">
              <Preconditions>
    
                <Precondition Type="ClaimsExist" ExecuteActionsIf="false">
                  <Value>extension_mustResetPassword</Value>
                  <Action>SkipThisOrchestrationStep</Action>
                </Precondition>            
                <Precondition Type="ClaimEquals" ExecuteActionsIf="false">
                  <Value>extension_mustResetPassword</Value>
                  <Value>True</Value>
                  <Action>SkipThisOrchestrationStep</Action>
                </Precondition>            
              </Preconditions>        
              <ClaimsExchanges>
                <ClaimsExchange Id="AADUserRemoveMustResetPasswordUsingObjectId" TechnicalProfileReferenceId="AAD-UserRemoveMustResetPasswordUsingObjectId" />
              </ClaimsExchanges>
            </OrchestrationStep>
    
     
            <OrchestrationStep Order="6" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
          </OrchestrationSteps>
          <ClientDefinition ReferenceId="DefaultWeb" />
        </UserJourney>
     
        </UserJourneys>
    
    </TrustFrameworkPolicy>

Object details in Azure

[
  {
    "accountEnabled": true,
    "assignedLicenses": [],
    "assignedPlans": [],
    "businessPhones": [],
    "createdDateTime": "2020-12-17T08:22:17+00:00",
    "creationType": "LocalAccount",
    "deviceKeys": [],
    "displayName": "DevM8",
    "givenName": "Dev",
    "identities": [
      {
        "signInType": "emailAddress",
        "issuer": "tenantId.onmicrosoft.com",
        "issuerAssignedId": "[email protected]",
        "@odata.type": "microsoft.graph.objectIdentity"
      },
      {
        "signInType": "userPrincipalName",
        "issuer": "tenantId.onmicrosoft.com",
        "issuerAssignedId": "[email protected]",
        "@odata.type": "microsoft.graph.objectIdentity"
      }
    ],
    "imAddresses": [],
    "mailNickname": "a2a5dbe2-7ba7-42a4-bd9a-67eb41c05d7e",
    "onPremisesExtensionAttributes": {
      "@odata.type": "microsoft.graph.onPremisesExtensionAttributes",
      "extensionAttribute1": null,
      "extensionAttribute2": null,
      "extensionAttribute3": null,
      "extensionAttribute4": null,
      "extensionAttribute5": null,
      "extensionAttribute6": null,
      "extensionAttribute7": null,
      "extensionAttribute8": null,
      "extensionAttribute9": null,
      "extensionAttribute10": null,
      "extensionAttribute11": null,
      "extensionAttribute12": null,
      "extensionAttribute13": null,
      "extensionAttribute14": null,
      "extensionAttribute15": null
    },
    "onPremisesProvisioningErrors": [],
    "otherMails": [],
    "passwordPolicies": "DisablePasswordExpiration",
    "provisionedPlans": [],
    "proxyAddresses": [],
    "refreshTokensValidFromDateTime": "2020-12-17T08:22:16+00:00",
    "signInSessionsValidFromDateTime": "2020-12-17T08:22:16+00:00",
    "surname": "M",
    "userPrincipalName": "[email protected]",
    "userType": "Member",
    "id": "a2a5dbe2-7ba7-42a4-bd9a-67eb41c05d7e",
    "@odata.type": "microsoft.graph.user",
    "deletedDateTime": null,
    "ageGroup": null,
    "city": null,
    "companyName": null,
    "consentProvidedForMinor": null,
    "country": null,
    "department": null,
    "employeeId": null,
    "employeeHireDate": null,
    "employeeOrgData": null,
    "employeeType": null,
    "faxNumber": null,
    "infoCatalogs": [],
    "isManagementRestricted": null,
    "isResourceAccount": null,
    "jobTitle": null,
    "legalAgeGroupClassification": null,
    "mail": null,
    "mobilePhone": null,
    "onPremisesDistinguishedName": null,
    "officeLocation": null,
    "onPremisesDomainName": null,
    "onPremisesImmutableId": null,
    "onPremisesLastSyncDateTime": null,
    "onPremisesSecurityIdentifier": null,
    "onPremisesSamAccountName": null,
    "onPremisesSyncEnabled": null,
    "onPremisesUserPrincipalName": null,
    "passwordProfile": null,
    "postalCode": null,
    "preferredDataLocation": null,
    "preferredLanguage": null,
    "showInAddressList": null,
    "state": null,
    "streetAddress": null,
    "usageLocation": null,
    "externalUserState": null,
    "externalUserStateChangeDateTime": null,
    "extension_185724b7875d4374904106f92b4b951e_FavouriteSeason": "summer",
    "extension_185724b7875d4374904106f92b4b951e_mustResetPassword": true,
    "extension_185724b7875d4374904106f92b4b951e_LovesPets": true
  }
]

AAD-Common Technical profile

 <TechnicalProfile Id="AAD-Common">
          <DisplayName>Azure Active Directory</DisplayName>
          <!--  Demo action required: Provide objectId and appId before using extension properties.
                For more information: https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-create-custom-attributes-profile-edit-custom 
                Action required: Insert objectId and appId here -->
          <Metadata>
            <Item Key="ApplicationObjectId">90aab09b-721e-4c95-b1e5-52266eb727a4</Item>
            <Item Key="ClientId">96e21f60-871b-48a0-867c-404c4ebfa6de</Item>
          </Metadata>
        </TechnicalProfile>


2
Hi, if the posted answer resolves your question, please mark it as the answer by clicking the check mark. Doing so helps others find answers to their questions.gsharma001
Show the payload you use to create or patch the user. Show the resulting user object via Graph API. ForceChangePasswordNextSignIn must be false. You can use this website to setup the prerequisites aka.ms/iefsetup. I suspect you didn’t configure AAD-Common in your XML files to R/W extension attributes.Jas Suri - MSFT
Added the code for creating user and TrustFrameworkExtensions.xml code in the questionDevM
Provide the exact object data for extensionInstance. Provide the exact AAD-Common Technical profile without censorship. The GUIDS must match.Jas Suri - MSFT
Updated the question with the azure user data and AAD-Common Technical Profile in TrustFrameworkExtensions.xmlDevM

2 Answers

1
votes

Thank you for your question.

As of now, the only possible way to require users to reset their passwords at first logon is by using custom policy: https://github.com/azure-ad-b2c/samples/tree/master/policies/force-password-reset-first-logon. So you are following the Correct way.

While creating local accounts in B2C via Graph API, the forceChangePasswordNextSignIn property must be set to false. Please see this DOC .

Could you please Check again.

0
votes

I had this exact issue and just figured it out, to use the AAD methods you have to include

<Metadata>
     <Item Key="ApplicationObjectId">00000000-0000-0000-0000-000000000000</Item>
     <Item Key="ClientId">00000000-0000-0000-0000-000000000000</Item>
</Metadata>

On each TechnicalProfile, so you need to add it to:

AAD-UserReadUsingObjectId  
AAD-UserRemoveMustResetPasswordUsingObjectId

I found if not included the AAD request fails, took me awhile debugging this with application insights hooked up, there is a really nice extension for VS code Azure AD B2C tools that lets you directly hook up to an app insights instance attached to your user flow, and it shows where an exception occurs in the flow. Without this you get almost 0 feedback on the internal errors