Azure B2C custom policy with Rest API not sending objectId in request

Viewed 757

I'm trying to setup a custom Azure B2C policy. The policy sends the request to the endpoint but doesn't populate the objectId in the body

    <ClaimsProvider>
      <DisplayName>REST API SignUp</DisplayName>
      <TechnicalProfiles>

        <!-- Custom Restful service -->
        <TechnicalProfile Id="REST-API-SignUp">
          <DisplayName>Validate user's input data and Save details to Web API</DisplayName>
          <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
          <Metadata>
            <Item Key="ServiceUrl">https://xxxxxxxxxxx.azurewebsites.net/api/Users/RegisterNewUser</Item>
            <Item Key="AllowInsecureAuthInProduction">true</Item>
            <Item Key="AuthenticationType">None</Item>
            <Item Key="SendClaimsIn">Body</Item>
          </Metadata>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="objectId" />
            <InputClaim ClaimTypeReferenceId="email" />
            <InputClaim ClaimTypeReferenceId="displayName"/>
            <InputClaim ClaimTypeReferenceId="surname" />
            <InputClaim ClaimTypeReferenceId="phoneNumber" />
          </InputClaims>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="roles" PartnerClaimType="roles" />
          </OutputClaims>
          <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
        </TechnicalProfile>

        <!-- Add validation technical profile to LocalAccountSignUpWithLogonEmail technical profile -->
        <TechnicalProfile Id="LocalAccountSignUpWithLogonEmail">
          <Metadata>
            <Item Key="ContentDefinitionReferenceId">api.localaccountsignup</Item>
          </Metadata>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="roles" PartnerClaimType="roles" DefaultValue="" />
          </OutputClaims>
          <ValidationTechnicalProfiles>
            <ValidationTechnicalProfile ReferenceId="REST-API-SignUp" />
          </ValidationTechnicalProfiles>
        </TechnicalProfile>

      </TechnicalProfiles>
    </ClaimsProvider>
  </ClaimsProviders>

All the other InputClaims gets populated in the request body except objectId

{"email":"sakkiexxx@xxxxch","displayName":"Sakkie","surname":"NA","phoneNumber":"9876543215"}

I have the following orchestration steps

 <UserJourney Id="SignUp">

      <OrchestrationSteps>
        <!-- Track that we have received a sign in request -->
        <OrchestrationStep Order="1" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="TrackSignUp-RequestReceived" TechnicalProfileReferenceId="AppInsights-SignUpRequest" />
          </ClaimsExchanges>
        </OrchestrationStep>

        <OrchestrationStep Order="2" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail" />
          </ClaimsExchanges>
        </OrchestrationStep>
        ...

and then the LocalAccountSignUpWithLogonEmail technical profile outputs the object Id

<ClaimsProvider>
      <DisplayName>Local Account</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="LocalAccountSignUpWithLogonEmail">
          <DisplayName>Email signup</DisplayName>
          <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
          <Metadata>
            <Item Key="IpAddressClaimReferenceId">IpAddress</Item>
            <Item Key="ContentDefinitionReferenceId">api.localaccountsignup</Item>
            <Item Key="language.button_continue">Sign Up</Item>
          </Metadata>
          <CryptographicKeys>
            <Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
          </CryptographicKeys>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="email" />
          </InputClaims>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="objectId" Required="true" />
            <OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="Verified.Email" Required="true" />
            <OutputClaim ClaimTypeReferenceId="newPassword" Required="true" />
            <OutputClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
            <OutputClaim ClaimTypeReferenceId="executed-SelfAsserted-Input" DefaultValue="true" />
            <OutputClaim ClaimTypeReferenceId="authenticationSource" />
            <OutputClaim ClaimTypeReferenceId="newUser" />
            <!-- Optional claims, to be collected from the user -->
            <OutputClaim ClaimTypeReferenceId="displayName" />
            <!-- <OutputClaim ClaimTypeReferenceId="givenName" /> -->
            <OutputClaim ClaimTypeReferenceId="surname" />
            <OutputClaim ClaimTypeReferenceId="phoneNumber" />
          </OutputClaims>
          <ValidationTechnicalProfiles>
            <ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonEmail" />
          </ValidationTechnicalProfiles>
          <UseTechnicalProfileForSessionManagement ReferenceId="SM-AAD" />
        </TechnicalProfile>
        ............
3 Answers

I update my user journey to include the API call as a separate step, this sorted out the objectId

 <UserJourney Id="SignUp">

      <OrchestrationSteps>
        <!-- Track that we have received a sign in request -->
        <OrchestrationStep Order="1" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="TrackSignUp-RequestReceived" TechnicalProfileReferenceId="AppInsights-SignUpRequest" />
          </ClaimsExchanges>
        </OrchestrationStep>

        <OrchestrationStep Order="2" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail" />
          </ClaimsExchanges>
        </OrchestrationStep>

        <OrchestrationStep Order="3" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="Web-API-Update" TechnicalProfileReferenceId="REST-API-SignUp" />
          </ClaimsExchanges>
        </OrchestrationStep>

Adding an extra step to your User Journey is one of the options. You could also use a ValidationProfile as an alternative, in that case you would change the following XML

<ValidationTechnicalProfiles>
    <ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonEmail" />
</ValidationTechnicalProfiles>

To this

<ValidationTechnicalProfiles>
    <ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonEmail" />
    <ValidationTechnicalProfile ReferenceId="REST-API-SignUp" />
</ValidationTechnicalProfiles>

There is one 'but' and that is that both solutions have a downside. The ObjectId is generated in technical profile AAD-UserWriteUsingLogonEmail. Which means that your account is created in Azure AD. If your next step or validation profile failed to call the API you might end up in a state where the account is registered but the object id is not stored in your API. This is also the other way around, if your API is providing additional claims to the user, these are not saved on the users' profile.

More info about the technical profile AAD-UserWriteUsingLogonEmail can be found on the official docs:
https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-technical-profile#azure-ad-technical-provider-operations

Same for Validation Technical Profiles:
https://docs.microsoft.com/en-us/azure/active-directory-b2c/validation-technical-profile

This is exactly what happens for me too. I tried adding the AAD-UserWriteUsingLogonEmail as a first node in the validation technical profiles in the TrustFrameworkExtensions.xml file (note that in the TrustFrameworkBase.xml, the AAD-UserWriteUsingLogonEmail already exists. I don't want to change the base as its not recommended.

In TrustFrameworkExtensions.xml (for LocalAccountsSignUpWithLogonEmail)

<ValidationTechnicalProfiles>
    <ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonEmail" />
    <ValidationTechnicalProfile ReferenceId="REST-API-SignUp" />
</ValidationTechnicalProfiles>

In TrustFrameworkBase.xml (for LocalAccountsSignUpWithLogonEmail)

 <ValidationTechnicalProfiles>
    <ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonEmail"/> 
 </ValidationTechnicalProfiles>

but what happens is that I think the the AAD-UserWriteUsingLogonEmail gets called twice. once in the extension and then once in the base.

Does anyone actually know how this inheritance or overriding work? If you define something in the extension xml, does it overwrite or get merged? if so in what order? it seems that the base node gets called first, so that's why oid isn't populated. but then when you put it in the extension as a first node (you want it to overwrite the base), the base gets called again. is there anyway to change the order or say "I want this to replace (override) what is in the base? Is it merged? There is no documentation on how base policies work.

I will try changing the journey as recommended, but would still like to know how having a base policy works, if anyone can answer

Related