1
votes

I have B2C custom policy signin UserJouney which checks to see if the user requires a password reset on their first logon. We are using an extension attribute to do this as B2C has a bug where the "forceChangePasswordNextLogin" value prevents the user from logging in at all.

Here is the sign in user journey.

<UserJourney Id="SignUpOrSignInSaml">
  <OrchestrationSteps>
    <OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
      <ClaimsProviderSelections>
        <ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninUsernameExchange" />
      </ClaimsProviderSelections>
      <ClaimsExchanges>
        <ClaimsExchange Id="LocalAccountSigninUsernameExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Username" />
      </ClaimsExchanges>
    </OrchestrationStep>
    <OrchestrationStep Order="2" Type="ClaimsExchange">
      <Preconditions>
        <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
          <Value>objectId</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsExchanges>
        <ClaimsExchange Id="SignUpWithLogonUsernameExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonName" />
      </ClaimsExchanges>
    </OrchestrationStep>
    <!-- This step reads any user attributes that we may not have received when in the token. -->
    <OrchestrationStep Order="3" Type="ClaimsExchange">
      <ClaimsExchanges>
        <ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
      </ClaimsExchanges>
    </OrchestrationStep>
    <OrchestrationStep Order="4" Type="ClaimsExchange">
      <Preconditions>
        <Precondition Type="ClaimEquals" ExecuteActionsIf="false">
          <Value>extension_ChangePasswordRequired</Value>
          <Value>true</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
        </Preconditions>
        <ClaimsExchanges>
        <ClaimsExchange Id="NewCredentials" TechnicalProfileReferenceId="LocalAccountWritePasswordChangeUsingObjectId" />
      </ClaimsExchanges>
    </OrchestrationStep>
    <OrchestrationStep Order="5" Type="ClaimsExchange">
    <ClaimsExchanges>
      <ClaimsExchange Id="UpdatePasswordResetValue" TechnicalProfileReferenceId="LocalAccountUpdatePasswordResetStateValue" />
    </ClaimsExchanges>
  </OrchestrationStep>
    <OrchestrationStep Order="6" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="Saml2AssertionIssuer" />
  </OrchestrationSteps>
  <ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>

Step 4 in the UserJourney evaluates whether the extension attribute "extension_ChangePasswordRequired" is set to "true" and will prompt the user to change their password if it reads "true". This is working fine.

Step 5 is then used to update the extension attribute to something other than "true" so the user isn't prompted again at next login however doesn't seem to be working.

Here is my "LocalAccountUpdatePasswordResetStateValue" TechnicalProfile

    <TechnicalProfile Id="LocalAccountUpdatePasswordResetStateValue">
        <DisplayName>Update Password Set Value</DisplayName>
        <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
        <OutputClaims>
          <OutputClaim ClaimTypeReferenceId="extension_ChangePasswordRequired" Required="true" />
        </OutputClaims>
        <OutputClaimsTransformations>
          <OutputClaimsTransformation ReferenceId="SetPasswordResetStatus" />
        </OutputClaimsTransformations>
        <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
      </TechnicalProfile>

And here is the Output claims transformation that it is calling

<ClaimsTransformation Id="SetPasswordResetStatus" TransformationMethod="FormatStringClaim">
      <InputClaims>
        <InputClaim ClaimTypeReferenceId="extension_ChangePasswordRequired" TransformationClaimType="inputClaim" />
      </InputClaims>
      <InputParameters>
        <InputParameter Id="stringFormat" DataType="string" Value="abc123" />
      </InputParameters>
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="extension_ChangePasswordRequired" TransformationClaimType="outputClaim" />
      </OutputClaims>
    </ClaimsTransformation>

The policies pass validation at time of upload however doesn't set the extension attribute on the user after a password reset.

Does anyone know what I'm doing wrong here or if there is a better way of achieving this?

-----Update-----

I'm successfully able to write a value to a different extension attribute via a persisted claim as seen here

<TechnicalProfile Id="AAD-UserUpdateStateValue">
   <Metadata>
      <Item Key="Operation">Write</Item>
      <Item Key="RaiseErrorIfClaimsPrincipalAlreadyExists">false</Item>
      <Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
   </Metadata>
   <IncludeInSso>false</IncludeInSso>
 <InputClaims>
   <InputClaim ClaimTypeReferenceId="objectId" Required="true" />
 </InputClaims>
 <PersistedClaims>
    <!-- Required claims -->
    <PersistedClaim ClaimTypeReferenceId="objectId" />
    <!-- Optional claims -->
    <PersistedClaim ClaimTypeReferenceId="extension_Flag" DefaultValue="abc1234567"/>
    </PersistedClaims>
  <IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>

However as mentioned by Chris in this post this doesn't work if I have read the claim in a previous step.

1

1 Answers

3
votes

The DefaultValue attribute is effective if and only if the claim value isn't set.

To force the use of a default value, set the AlwaysUseDefaultValue attribute to true:

<PersistedClaim ClaimTypeReferenceId="extension_ChangePasswordRequired" DefaultValue="true" AlwaysUseDefaultValue="true" />

In your particular case, you should set the extension_ChangePasswordRequired claim to this default value in the AAD-UserWritePasswordUsingObjectId technical profile as the new password is written:

<TechnicalProfile Id="AAD-UserWritePasswordUsingObjectId">
  <Metadata>
    <Item Key="Operation">Write</Item>
    <Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
  </Metadata>
  <IncludeInSso>false</IncludeInSso>
  <InputClaims>
    <InputClaim ClaimTypeReferenceId="objectId" Required="true" />
  </InputClaims>
  <PersistedClaims>
    <PersistedClaim ClaimTypeReferenceId="objectId" />
    <PersistedClaim ClaimTypeReferenceId="newPassword" PartnerClaimType="password" />
    <PersistedClaim ClaimTypeReferenceId="extension_ChangePasswordRequired" DefaultValue="true" AlwaysUseDefaultValue="true" />
  </PersistedClaims>
  <IncludeTechnicalProfile ReferenceId="AAD-Common" />
</TechnicalProfile>

You can then remove orchestration step 5 from the user journey.