azure-ad-b2c / samples

Azure AD B2C custom policy solutions and samples.
840 stars 597 forks source link

Force Password Reset with Seamless Account Migraiton #484

Open EnmanuelParedesR opened 1 year ago

EnmanuelParedesR commented 1 year ago

Hello.

I'm already using both of these policies (merged as one) and everything is good except for one scenario: if my flag "requiresMigration" is still active and if The forceChangePasswordNextLogin is also active, my policy would go and execute the requiresMigration and compare the password with the one inserted by the user (which would not be correct because we already give them an OTP password using the console). I try to read the variable but I'm not able to accomplish it.

<TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Email">
          <ValidationTechnicalProfiles>
            <!--Add user migration validation technical profiles before login-NonInteractive -->
            <!-- Populate extension_requireMigration into the claims pipeline -->
            <ValidationTechnicalProfile ReferenceId="Get-requiresMigration-status-signin" ContinueOnError="false" />
            <!-- If extension_requireMigration is true, call the legacy IdP via the REST API -->
            <ValidationTechnicalProfile ReferenceId="UserMigrationViaLegacyIdp" ContinueOnError="false">
              <Preconditions>
                <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
                  <!-- Skip this step if change password is required. -->
                  <Value>forceChangePasswordNextLogin</Value>
                  <Value>True</Value>

                  <Action>SkipThisValidationTechnicalProfile</Action>
                </Precondition>
                <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
                  <Value>requiresMigration</Value>
                  <Value>False</Value>
                  <Action>SkipThisValidationTechnicalProfile</Action>
                </Precondition>

              </Preconditions>
            </ValidationTechnicalProfile>
            <!-- If the API returned 'tokensuccess', write the new password and unmark the account for migration -->
            <ValidationTechnicalProfile ReferenceId="AAD-WritePasswordAndFlipMigratedFlag" ContinueOnError="false">
              <Preconditions>
                <Precondition Type="ClaimsExist" ExecuteActionsIf="false">
                  <Value>tokenSuccess</Value>
                  <Action>SkipThisValidationTechnicalProfile</Action>
                </Precondition>
              </Preconditions>
            </ValidationTechnicalProfile>
            <!-- Initiate a normal logon against Azure AD B2C -->
            <ValidationTechnicalProfile ReferenceId="login-NonInteractive" />
          </ValidationTechnicalProfiles> 
        </TechnicalProfile>

I'm trying to ignore the API Request in this technical profile because the password to compare would be the OTP one.

I'm also trying to retrieve the passwordProfile.forceChangePasswordNextLogin but as I check is not listed in the User Profiles Attributes, so, how can I get it and ignore this execution?

Adding pictures of the scenario:

1 - The requires migration flag is on (so the login should call the API to compare with the old provider) image

2 - BUT, a temporary password is given to the user:

image

In this case, this means the passwordProfile.forceChangePasswordNextLogin is true, but if we execute the login with the password of step 2:

image

If I use the password of the old IDP the screen for the password reset appears, but I want to ignore the old password if they give them a new one.

It's also good to know that if I try to upload my Signup I get this error

_Output Claim 'passwordExpired' is not supported in Azure Active Directory Provider technical profile 'Get-requiresMigration-status-signin' of policy 'B2C_1A_SeamlessMigrationsignuporsignin

I try with passwordExpired and passwordProfile.forceChangePasswordNextLogin. In my other extension this ones are retrieve but using the login-interactive.


@JasSuri Can you probably help me with this? I already implement 95% of the functionality, I just need to resolve this last one.

EnmanuelParedesR commented 1 year ago

One possible solution would be to call an API and retrieve the forceChangePasswordNextSignIn value and with it, this should work I guess, but I'll like to avoid making an Az Function to get something that is 'already' there.

JasSuri commented 1 year ago

You should be trying to read the forceChangePasswordNextLogin attribute in Get-requiresMigration-status-signin. The attribute name will be for Azure AD Graph, not MS Graph API. Make sure to check AppInsights logs to check if it is read back properly.

You need to modify login-noninteractive to support forceChangePasswordNextLogin attribute being set to true.

        <TechnicalProfile Id="login-NonInteractive">
          <InputClaims>
            <!-- Continue if the password is expired  -->
            <InputClaim ClaimTypeReferenceId="continueOnPasswordExpiration" DefaultValue="true" />
          </InputClaims>
          <OutputClaims>
            <!-- Indicates whether user needs to reset the password.
            If the value of this claim is true, other claims aren't return-->
            <OutputClaim ClaimTypeReferenceId="forceChangePasswordNextLogin" PartnerClaimType="passwordExpired" />
          </OutputClaims>
        </TechnicalProfile>

https://learn.microsoft.com/en-us/azure/active-directory-b2c/force-password-reset?pivots=b2c-custom-policy#configure-your-custom-policy

https://github.com/azure-ad-b2c/samples/blob/master/policies/force-password-reset/policy/TrustFrameworkExtensions_ForcePasswordReset.xml#L61

Policy looks fine otherwise. You should use policy to return forceChangePasswordNextLogin to false and set appropiate password policy attribute also.

EnmanuelParedesR commented 1 year ago

Hello. This is the definition of Get-requiresMigration-status-signin

<TechnicalProfile Id="Get-requiresMigration-status-signin">
          <Metadata>
            <Item Key="Operation">Read</Item>
            <Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
            <Item Key="UserMessageIfClaimsPrincipalDoesNotExist">An account could not be found for the provided user ID.</Item>
          </Metadata>
          <IncludeInSso>false</IncludeInSso>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="signInNames.emailAddress" Required="true" />
          </InputClaims>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="objectId" />
            <!-- Set a default value (false) in the case the account does not have this attribute defined -->
            <OutputClaim ClaimTypeReferenceId="requiresMigration" PartnerClaimType="extension_requiresMigration" DefaultValue="false" />
            <OutputClaim ClaimTypeReferenceId="extension_fbaGuid"/> 
            <!-- <OutputClaim ClaimTypeReferenceId="forceChangePasswordNextLogin" PartnerClaimType="passwordProfile.forceChangePasswordNextLogin"  DefaultValue="false" /> -->

          </OutputClaims>
          <IncludeTechnicalProfile ReferenceId="AAD-Common" />
        </TechnicalProfile>

Its commented because if a save it and then try to save my B2C_1A_SEAMLESSMIGRATION_SIGNUPORSIGNIN an error like is throw:

_is not supported in Azure Active Directory Provider technical profile 'Get-requiresMigration-status-signin' of policy 'B2C_1A_SeamlessMigration_signuporsignin'. If it is a claim with default value, add AlwaysUseDefaultValue="true" to the output claim mapping.Output Claim 'passwordProfile.forceChangePasswordNextLogin' is not supported in Azure Active Directory Provider technical profile 'Get-requiresMigration-status-signin' of policy 'B2C_1A_SeamlessMigrationsignuporsignin'. If it is a claim with default value, add AlwaysUseDefaultValue="true" to the output claim mapping.

My login-NonInteractive is interactive and those other profiles are in my other extension and work fine:

image

The problem with this (I guess) is that my login interactive is called after these triggers, because the requiresMigration flow first changes the password (if true) before calling it.

This is my complete technical profile

image

And, this is an example of the log. [The expected result should not call the API] this JSON is a bit confusing (even with the vs tool) I see the condition value (true) but I can find the value of the property itself, but I imaging is coming null.

[
  {
    "Kind": "Headers",
    "Content": {
      "UserJourneyRecorderEndpoint": "urn:journeyrecorder:applicationinsights",
      "CorrelationId": "bef2076a-f6fd-4bf6-bb85-39d0380d520f",
      "EventInstance": "Event:SELFASSERTED",
      "TenantId": "{tenant}.onmicrosoft.com",
      "PolicyId": "B2C_1A_SeamlessMigration_signuporsignin"
    }
  },
  {
    "Kind": "Transition",
    "Content": {
      "EventName": "SELFASSERTED",
      "StateName": "Initial"
    }
  },
  {
    "Kind": "Predicate",
    "Content": "Web.TPEngine.StateMachineHandlers.CrossSiteRequestForgeryValidationHandler"
  },
  {
    "Kind": "HandlerResult",
    "Content": {
      "Result": true,
      "Statebag": {
        "MACHSTATE": {
          "c": "2022-12-02T03:54:31.5964483Z",
          "k": "MACHSTATE",
          "v": "Initial",
          "p": true
        },
        "JC": {
          "c": "2022-12-02T03:54:27.7962671Z",
          "k": "JC",
          "v": "es",
          "p": true
        },
        "Complex-CLMS": {
          "passwordPolicies": "DisablePasswordExpiration, DisableStrongPassword"
        },
        "ORCH_CS": {
          "c": "2022-12-02T03:54:37.7108047Z",
          "k": "ORCH_CS",
          "v": "1",
          "p": true
        },
        "ORCH_IDX": {
          "c": "2022-12-02T03:54:27.999391Z",
          "k": "ORCH_IDX",
          "v": "0",
          "p": true
        },
        "RA": {
          "c": "2022-12-02T03:54:27.999391Z",
          "k": "RA",
          "v": "0",
          "p": true
        },
        "RPP": {
          "c": "2022-12-02T03:54:27.780644Z",
          "k": "RPP",
          "v": "OAUTH2",
          "p": true
        },
        "RPIPP": {
          "c": "2022-12-02T03:54:27.780644Z",
          "k": "RPIPP",
          "v": "OAuth2ProtocolProvider",
          "p": true
        },
        "OTID": {
          "c": "2022-12-02T03:54:27.780644Z",
          "k": "OTID",
          "v": "{tenant}.onmicrosoft.com",
          "p": true
        },
        "APPMV": {
          "c": "2022-12-02T03:54:27.7962671Z",
          "k": "APPMV",
          "v": "V2",
          "p": true
        },
        "IC": {
          "c": "2022-12-02T03:54:27.999391Z",
          "k": "IC",
          "v": "True",
          "p": true
        },
        "MSG(f795224b-205d-4877-9d71-f17539c129e5)": {
          "c": "2022-12-02T03:54:27.999391Z",
          "k": "MSG(f795224b-205d-4877-9d71-f17539c129e5)",
          "v": "{\"TenantId\":\"{tenant}.onmicrosoft.com\",\"PolicyId\":\"B2C_1A_SeamlessMigration_signuporsignin\",\"RedirectUri\":\"https://{idp}.azurewebsites.net/signin-oidc-pswd-change\",\"AdditionalParameters\":{\"p\":\"B2C_1A_SEAMLESSMIGRATION_SIGNUPORSIGNIN\"},\"Nonce\":\"defaultNonce\",\"ClientId\":\"2aedada7-0bd8-4a3e-b274-9d22bf96f281\",\"ResponseType\":\"id_token\",\"ResponseRedirector\":{\"URI\":\"https://{idp}.azurewebsites.net/signin-oidc-pswd-change\",\"D\":false,\"WF\":true,\"R\":false},\"Scope\":\"openid\",\"AppModelVersion\":1,\"ScopedProviders\":[]}",
          "p": true,
          "t": "OAuth2"
        },
        "IMESSAGE": {
          "c": "2022-12-02T03:54:27.999391Z",
          "k": "IMESSAGE",
          "v": "f795224b-205d-4877-9d71-f17539c129e5",
          "p": true
        },
        "EID": {
          "c": "2022-12-02T03:54:28.0150159Z",
          "k": "EID",
          "v": "urn:com:microsoft:aad:b2c:elements:contract:unifiedssp:2.1.5",
          "p": true
        },
        "SE": {
          "c": "2022-12-02T03:54:31.5964483Z",
          "k": "SE",
          "v": "Self-asserted_local",
          "p": true
        },
        "CMESSAGE": {
          "c": "2022-12-02T03:54:37.7108047Z",
          "k": "CMESSAGE",
          "v": "f795224b-205d-4877-9d71-f17539c129e5",
          "p": true
        },
        "ComplexItems": "_MachineEventQ, REPRM, TCTX"
      },
      "PredicateResult": "True"
    }
  },
  {
    "Kind": "Predicate",
    "Content": "Web.TPEngine.StateMachineHandlers.IsDisplayControlActionRequestHandler"
  },
  {
    "Kind": "HandlerResult",
    "Content": {
      "Result": false,
      "PredicateResult": "False"
    }
  },
  {
    "Kind": "Predicate",
    "Content": "Web.TPEngine.StateMachineHandlers.IsClaimVerificationRequestHandler"
  },
  {
    "Kind": "HandlerResult",
    "Content": {
      "Result": true,
      "PredicateResult": "False"
    }
  },
  {
    "Kind": "Predicate",
    "Content": "Web.TPEngine.StateMachineHandlers.SelfAssertedMessageValidationHandler"
  },
  {
    "Kind": "HandlerResult",
    "Content": {
      "Result": false,
      "RecorderRecord": {
        "Values": [
          {
            "Key": "Validation",
            "Value": {
              "Values": [
                {
                  "Key": "SubmittedBy",
                  "Value": null
                },
                {
                  "Key": "ProtocolProviderType",
                  "Value": "SelfAssertedAttributeProvider"
                },
                {
                  "Key": "TechnicalProfileEnabled",
                  "Value": {
                    "EnabledRule": "Always",
                    "EnabledResult": true,
                    "TechnicalProfile": "Get-requiresMigration-status-signin"
                  }
                },
                {
                  "Key": "ValidationTechnicalProfile",
                  "Value": {
                    "Values": [
                      {
                        "Key": "TechnicalProfileId",
                        "Value": "Get-requiresMigration-status-signin"
                      },
                      {
                        "Key": "MappingPartnerTypeForClaim",
                        "Value": {
                          "PartnerClaimType": "signInNames.emailAddress",
                          "PolicyClaimType": "signInName"
                        }
                      }
                    ]
                  }
                },
                {
                  "Key": "Precondition",
                  "Value": {
                    "$id": "1",
                    "Type": 1,
                    "ExecuteActionsIf": true,
                    "ActionTypes": [
                      1
                    ],
                    "Values": [
                      "forceChangePasswordNextLogin",
                      "True"
                    ]
                  }
                },
                {
                  "Key": "Precondition",
                  "Value": {
                    "$id": "2",
                    "Type": 1,
                    "ExecuteActionsIf": true,
                    "ActionTypes": [
                      1
                    ],
                    "Values": [
                      "requiresMigration",
                      "False"
                    ]
                  }
                },
                {
                  "Key": "TechnicalProfileEnabled",
                  "Value": {
                    "EnabledRule": "Always",
                    "EnabledResult": true,
                    "TechnicalProfile": "UserMigrationViaLegacyIdp"
                  }
                },
                {
                  "Key": "ValidationTechnicalProfile",
                  "Value": {
                    "Values": [
                      {
                        "Key": "TechnicalProfileId",
                        "Value": "UserMigrationViaLegacyIdp"
                      },
                      {
                        "Key": "MappingPartnerTypeForClaim",
                        "Value": {
                          "PartnerClaimType": "email",
                          "PolicyClaimType": "signInName"
                        }
                      },
                      {
                        "Key": "MappingPartnerTypeForClaim",
                        "Value": {
                          "PartnerClaimType": "password",
                          "PolicyClaimType": "password"
                        }
                      }
                    ]
                  }
                },
                {
                  "Key": "Exception",
                  "Value": {
                    "Kind": "Handled",
                    "HResult": "80131500",
                    "Message": "ErrorCodes: AADB2C",
                    "Data": {
                      "IsPolicySpecificError": false
                    }
                  }
                }
              ]
            }
          }
        ]
      },
      "Statebag": {
        "ComplexItems": "_MachineEventQ, REPRM, TCTX, S_CTP, M_EXCP"
      },
      "Exception": {
        "Kind": "Handled",
        "HResult": "80131500",
        "Message": "ErrorCodes: AADB2C",
        "Data": {
          "IsPolicySpecificError": false
        }
      },
      "PredicateResult": "False"
    }
  },
  {
    "Kind": "Action",
    "Content": "Web.TPEngine.StateMachineHandlers.SendRetryHandler"
  },
  {
    "Kind": "HandlerResult",
    "Content": {
      "Result": true
    }
  }
]