1

I am trying to configure forgot user Name flow using https://github.com/azure-ad-b2c/samples/tree/master/policies/username-discovery where users can provide their email and it will discover the user name and call REST technical profile within the policy to send an email, but keeps getting below error:

The claims exchange 'REST-RestoreUsername' specified in step '2' returned HTTP error response with Code 'BadRequest' and Reason 'Bad Request'.

not sure what is causing the problem.

JSON payload required by API { "toEmails": [ "string" ], "templateName": "string", "emailParam": [ "string" ] }

Not sure if claimstransformation is defined correctly

<ClaimsTransformation Id="GenerateRequestBody" TransformationMethod="GenerateJson">
    <InputClaims>
      <InputClaim ClaimTypeReferenceId="email" TransformationClaimType="toEmails" />
      <InputClaim ClaimTypeReferenceId="forgotuserid" TransformationClaimType="templateName" />
      <InputClaim ClaimTypeReferenceId="givenName" TransformationClaimType="emailParam" />
      <InputClaim ClaimTypeReferenceId="surname" TransformationClaimType="emailParam" />
      <InputClaim ClaimTypeReferenceId="signInNames.userName" TransformationClaimType="emailParam" />
    </InputClaims>
    <OutputClaims>
      <OutputClaim ClaimTypeReferenceId="forgotuserid" TransformationClaimType="outputClaim" />
    </OutputClaims>
  </ClaimsTransformation>

TP:

 <!-- this technical profile collects the user's email address, call
        validation technical profile to discover the user name and send the 
        email with the username to the end user -->
    <TechnicalProfile Id="SelfAsserted-UsernameDiscovery">
      <DisplayName>Forgot your username?</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.selfasserted</Item>
      </Metadata>
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
        <OutputClaim ClaimTypeReferenceId="displayName" />
        <OutputClaim ClaimTypeReferenceId="surname" />
        <OutputClaim ClaimTypeReferenceId="signInNames.userName" />
      </OutputClaims>
      <ValidationTechnicalProfiles>
        <ValidationTechnicalProfile ReferenceId="AAD-UserReadUsingEmailAddress-NoError" />
        <ValidationTechnicalProfile ReferenceId="REST-RestoreUsername" ContinueOnError="true">
          <Preconditions>
            <Precondition Type="ClaimsExist" ExecuteActionsIf="false">
              <Value>signInNames.userName</Value>
              <Action>SkipThisValidationTechnicalProfile</Action>
            </Precondition>
          </Preconditions>
        </ValidationTechnicalProfile>
      </ValidationTechnicalProfiles>
      <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
    </TechnicalProfile>

    <TechnicalProfile Id="SelfAsserted-UserMessage">
      <DisplayName>UserNameDiscoveryMessage</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.selfasserted</Item>
        <!-- hide the continue and cancel buttons -->
        <Item Key="setting.showContinueButton">false</Item>
        <Item Key="setting.showCancelButton">false</Item>
      </Metadata>
      <InputClaimsTransformations>
        <InputClaimsTransformation ReferenceId="GetUserMessage" />
      </InputClaimsTransformations>
      <InputClaims>
        <InputClaim ClaimTypeReferenceId="userMessage" />
      </InputClaims>
      <OutputClaims>
        <!-- Show the paragraph claim with the message to the user -->
        <OutputClaim ClaimTypeReferenceId="userMessage" />
      </OutputClaims>
      <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
    </TechnicalProfile>

REST API TP:

<ClaimsProvider>
  <DisplayName>REST APIs</DisplayName>
  <TechnicalProfiles>
    <!-- Custom Restful service that sends emails -->
    <TechnicalProfile Id="REST-RestoreUsername">
      <DisplayName>Validate user input data and return loyaltyNumber claim</DisplayName>
      <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
      <Metadata>
        <!--Action required: value of the ServiceUrl according to your application location-->
        <Item Key="ServiceUrl">http://XXXXXXXXXXX/api/v1/Email/SendEmail</Item>
        <Item Key="SendClaimsIn">Body</Item>
        <Item Key="AuthenticationType">None</Item>
        <Item Key="allowInsecureAuthInProduction">true</Item>
        <Item Key="DebugMode">true</Item>
        <!--<Item Key="DefaultUserMessageIfRequestFailed">Cannot process your request right now, please try again later.</Item>-->
      </Metadata>
      <InputClaimsTransformations>
        <InputClaimsTransformation ReferenceId="GenerateRequestBody" />
      </InputClaimsTransformations>
      <InputClaims>
        <InputClaim ClaimTypeReferenceId="forgotuserid" />
      </InputClaims>
      <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
    </TechnicalProfile>
  </TechnicalProfiles>
</ClaimsProvider>

Orchestration Step:

<UserJourneys>
<UserJourney Id="SignUpOrSignInMFAOption" DefaultCpimIssuerTechnicalProfileReferenceId="JwtIssuer">
  <OrchestrationSteps>
    <OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
      <ClaimsProviderSelections>
        <ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninUsernameExchange" />
        <ClaimsProviderSelection TargetClaimsExchangeId="ForgotPasswordExchange" />
        <ClaimsProviderSelection TargetClaimsExchangeId="SelfAssertedUsernameDiscoveryExchange" />
      </ClaimsProviderSelections>
      <ClaimsExchanges>
        <ClaimsExchange Id="LocalAccountSigninUsernameExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Username" />
      </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="SignUpWithLogonUsernameExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonName" />
        <ClaimsExchange Id="ForgotPasswordExchange" TechnicalProfileReferenceId="ForgotPassword" />
        <ClaimsExchange Id="SelfAssertedUsernameDiscoveryExchange" TechnicalProfileReferenceId="SelfAsserted-UsernameDiscovery" />
      </ClaimsExchanges>
    </OrchestrationStep>

    <OrchestrationStep Order="3" Type="InvokeSubJourney">
      <Preconditions>
        <Precondition Type="ClaimsExist" ExecuteActionsIf="false">
          <Value>isForgotPassword</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <JourneyList>
        <Candidate SubJourneyReferenceId="PasswordReset" />
      </JourneyList>
    </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="4" Type="ClaimsExchange">
      <Preconditions>
        <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
          <Value>authenticationSource</Value>
          <Value>socialIdpAuthentication</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsExchanges>
        <ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
      </ClaimsExchanges>
    </OrchestrationStep>

    <OrchestrationStep Order="5" Type="ClaimsExchange">
      <Preconditions>
        <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
          <Value>extension_mfaByPhoneOrEmail</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsExchanges>
        <ClaimsExchange Id="SelfAsserted-Select-MFA-Method" TechnicalProfileReferenceId="SelfAsserted-Select-MFA-Method" />
      </ClaimsExchanges>
    </OrchestrationStep>

    <!-- Throw error if control was bypassed -->
    <OrchestrationStep Order="6" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="Return-MFA-Method-Incorrect-Error">
      <Preconditions>
        <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
          <Value>extension_mfaByPhoneOrEmail</Value>
          <Value>email</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
        <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
          <Value>extension_mfaByPhoneOrEmail</Value>
          <Value>phone</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
    </OrchestrationStep>

    <!-- Phone verification: If MFA is not required, the next three steps (#5-#7) should be removed.
         This step checks whether there's a phone number on record,  for the user. If found, then the user is challenged to verify it. -->
    <OrchestrationStep Order="7" Type="ClaimsExchange">
      <Preconditions>
        <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
          <Value>isActiveMFASession</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
        <!-- If the preferred MFA method is not 'phone' skip this orchestration step-->
        <Precondition Type="ClaimEquals" ExecuteActionsIf="false">
          <Value>extension_mfaByPhoneOrEmail</Value>
          <Value>phone</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsExchanges>
        <ClaimsExchange Id="PhoneFactor-Verify" TechnicalProfileReferenceId="PhoneFactor-InputOrVerify" />
      </ClaimsExchanges>
    </OrchestrationStep>

    <!-- Save MFA phone number: The precondition verifies whether the user provided a new number in the 
         previous step. If so, then the phone number is stored in the directory for future authentication 
         requests. -->
    <OrchestrationStep Order="8" Type="ClaimsExchange">
      <Preconditions>
        <Precondition Type="ClaimsExist" ExecuteActionsIf="false">
          <Value>newPhoneNumberEntered</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsExchanges>
        <ClaimsExchange Id="AADUserWriteWithObjectId" TechnicalProfileReferenceId="AAD-UserWritePhoneNumberUsingObjectId" />
      </ClaimsExchanges>
    </OrchestrationStep>

    <!-- MFA with email-->
    <OrchestrationStep Order="9" Type="ClaimsExchange">
      <Preconditions>
        <Precondition Type="ClaimEquals" ExecuteActionsIf="false">
          <Value>extension_mfaByPhoneOrEmail</Value>
          <Value>email</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsExchanges>
        <ClaimsExchange Id="Email-Verify" TechnicalProfileReferenceId="EmailVerifyOnSignIn" />
      </ClaimsExchanges>
    </OrchestrationStep>

    <!-- check if change password is required. If yes, ask the user to reset the password-->
    <OrchestrationStep Order="10" Type="ClaimsExchange">
      <Preconditions>
        <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
          <Value>authenticationSource</Value>
          <Value>socialIdpAuthentication</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
        <Precondition Type="ClaimEquals" ExecuteActionsIf="true">
          <Value>skipPasswordReset</Value>
          <Value>True</Value>
          <Action>SkipThisOrchestrationStep</Action>
        </Precondition>
      </Preconditions>
      <ClaimsExchanges>
        <ClaimsExchange Id="NewCredentials" TechnicalProfileReferenceId="LocalAccountWritePasswordUsingObjectId" />
      </ClaimsExchanges>
    </OrchestrationStep>

    <OrchestrationStep Order="11" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
  </OrchestrationSteps>
  <ClientDefinition ReferenceId="DefaultWeb" />
</UserJourney>
t_t
  • 19
  • 3

2 Answers2

1

Have you compiled the REST API in the sample and deployed it somewhere that is publically accessible?

Does it work if you test it with Postman?

The sample uses SendGrid. Have you set SendGrid up correctly?

Update

Based on the payload, it would look something like this:

<ClaimsTransformation Id="GenerateRequestBody" TransformationMethod="GenerateJson">
    <InputClaims>
        <InputClaim ClaimTypeReferenceId="email" TransformationClaimType="toEmails.0.email"/>       
        <InputClaim ClaimTypeReferenceId="forgotuserid" TransformationClaimType="templateName"/>
        <InputClaim ClaimTypeReferenceId="givenName" TransformationClaimType="emailParam.0.givenName"/>
        <InputClaim ClaimTypeReferenceId="surname" TransformationClaimType="emailParam.0.surname"/>
        <InputClaim ClaimTypeReferenceId="signInNames.userName" TransformationClaimType="emailParam.0.userName"/>
    </InputClaims>
    <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="request" TransformationClaimType="outputClaim"/>
    </OutputClaims>
</ClaimsTransformation>

It would be useful to see a sample of the expected output.

Update 2

<ClaimsTransformation Id="GenerateRequestBody" TransformationMethod="GenerateJson">
    <InputClaims>
        <InputClaim ClaimTypeReferenceId="email" TransformationClaimType="toEmails.0.email"/>
        <InputClaim ClaimTypeReferenceId="givenName" TransformationClaimType="emailParam.0.givenName"/>
        <InputClaim ClaimTypeReferenceId="surname" TransformationClaimType="emailParam.0.surname"/>
        <InputClaim ClaimTypeReferenceId="signInNames.userName" TransformationClaimType="emailParam.0.userName"/>
    </InputClaims>
    <InputParameters>
        <InputParameter Id="templateName"" DataType="string" Value="forgotuserid"/>         
    </InputParameters>
    <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="request" TransformationClaimType="outputClaim"/>
    </OutputClaims>
</ClaimsTransformation>
rbrayb
  • 46,440
  • 34
  • 114
  • 174
  • I have updated the policy in my question – t_t Aug 31 '22 at 09:23
  • Have you compiled the REST API in the sample and deployed it somewhere that is publically accessible? - Yes Does it work if you test it with Postman? - Yes The sample uses SendGrid. Have you set SendGrid up correctly? - Using mailgun API – t_t Aug 31 '22 at 09:58
  • thanks for the correction,but forgotuserid is my template name, which is coming like this in appinsight: `{ "Key": "InputClaim", "Value": { "PolicyClaimType": "forgotuserid", "Value": "undefined" }` – t_t Sep 01 '22 at 09:17
  • is this you want to see `{ "toEmails": [ Superman@xxxxxxx.com ], "templateName": "forgotuserid", "emailParam": [ "firstName","LastName","UserName" ] }` – t_t Sep 01 '22 at 16:28
0

@rbrayb - thank you for your help, much appreciated. have used below CT.

 <ClaimsTransformation Id="CreateJSONBody" TransformationMethod="GenerateJson">
  <InputClaims>
    <InputClaim ClaimTypeReferenceId="email" TransformationClaimType="toEmails.0" />
    <InputClaim ClaimTypeReferenceId="givenName" TransformationClaimType="emailParam.0" />
    <InputClaim ClaimTypeReferenceId="surName" TransformationClaimType="emailParam.1" />
    <InputClaim ClaimTypeReferenceId="signInNames.userName" TransformationClaimType="emailParam.2"/>
  </InputClaims>
  <InputParameters>
    <InputParameter Id="templateName" DataType="string" Value="forgotuserid" />
  </InputParameters>
  <OutputClaims>
    <OutputClaim ClaimTypeReferenceId="RequestPayload" TransformationClaimType="outputClaim" />
  </OutputClaims>
</ClaimsTransformation>

this has fixed the issue.

Thanks again.

t_t
  • 19
  • 3