1

We are building an IdP-initiated SSO flow using Azure AD B2C custom policies, where the journey must:

  1. Read 3 querystring values:
    enc_attrs_token, sp, and EntityId

  2. Pass them to a backend REST API via APIM

  3. Use the backend response to construct the final SAML assertion

The incoming URL (masked) looks like:

https://<tenant>.b2clogin.com/<tenant>.onmicrosoft.com/B2C_1A_IdpSso/generic/login?EntityId=TP&enc_attrs_token=_TEST&sp=HD

What we tried

We call the backend using a REST technical profile as soon as the journey starts (Order="1").
We expected {QueryString:*} tokens to resolve before the REST body is serialized.

UserJourney

<OrchestrationStep Order="1" Type="ClaimsExchange">
  <ClaimsExchanges>
    <ClaimsExchange Id="GetAttributes"
    TechnicalProfileReferenceId="REST-GetAssertionAttributes-UAT" />
  </ClaimsExchanges>
</OrchestrationStep>

REST Technical Profile

<TechnicalProfile Id="REST-GetAssertionAttributes-UAT">
  <Metadata>
    <Item Key="ServiceUrl">https://<masked-host>/idp/sso/assertion-attributes?subscription-key=<masked></Item>
    <Item Key="SendClaimsIn">Body</Item>
    <Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>
    <Item Key="HttpBinding">POST</Item>
  </Metadata>

  <InputClaims>
    <InputClaim ClaimTypeReferenceId="enc_attrs_token" PartnerClaimType="encryptedToken"
                DefaultValue="{QueryString:enc_attrs_token}" AlwaysUseDefaultValue="true" />
    <InputClaim ClaimTypeReferenceId="serviceProviderCode" PartnerClaimType="serviceProviderCode"
                DefaultValue="{QueryString:sp}" AlwaysUseDefaultValue="true" />
    <InputClaim ClaimTypeReferenceId="entityIdFromQuery" PartnerClaimType="entityIdFromQuery"
                DefaultValue="{QueryString:EntityId}" AlwaysUseDefaultValue="true" />
  </InputClaims>
</TechnicalProfile>

What the backend actually receives (logs)

Instead of resolved values, we receive literal tokens:

{
  "encryptedToken": "{QueryString:enc_attrs_token}",
  "serviceProviderCode": "{QueryString:sp}",
  "entityIdFromQuery": "{QueryString:EntityId}"
}

What we expected

{
  "encryptedToken": "_TEST",
  "serviceProviderCode": "HD",
  "entityIdFromQuery": "TP"
}

Issue

{QueryString:*} tokens are not getting resolved when the REST TP runs as the first orchestration step.
Even with:

<Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>

Is there a limitation in Azure AD B2C that prevents {QueryString:*} claim resolution inside a REST Technical Profile when it is executed as the very first orchestration step?

And what is the minimal change required so that the querystring values resolve before being serialized into the REST request body?

here is complete policy for reference

<TrustFrameworkPolicy xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
PolicySchemaVersion="0.3.0.0" 
TenantId="***.onmicrosoft.com" 
PolicyId="B2C_1A_TrustFrameworkExtensions.IdpSso" 
PublicPolicyUri="http://***.onmicrosoft.com/TrustFrameworkExtensions.IdpSso" 
TenantObjectId="***-***-***-***-************">
  <BasePolicy>
    <TenantId>***.onmicrosoft.com</TenantId>
    <PolicyId>B2C_1A_TRUSTFRAMEWORKBASE</PolicyId>
  </BasePolicy>
  <BuildingBlocks>
    <ClaimsSchema>
      <ClaimType Id="enc_attrs_token">
        <DisplayName>Encrypted Attributes Token</DisplayName>
        <DataType>string</DataType>
      </ClaimType>
      <ClaimType Id="serviceProviderCode">
        <DisplayName>Service Provider Code</DisplayName>
        <DataType>string</DataType>
      </ClaimType>
      <ClaimType Id="attribute_firstname">
        <DisplayName>First name</DisplayName>
        <DataType>string</DataType>
      </ClaimType>
      <ClaimType Id="attribute_lastname">
        <DisplayName>Last name</DisplayName>
        <DataType>string</DataType>
      </ClaimType>
      <ClaimType Id="attribute_birthdate">
        <DisplayName>Birthdate</DisplayName>
        <DataType>string</DataType>
      </ClaimType>
      <ClaimType Id="attribute_gender">
        <DisplayName>Gender</DisplayName>
        <DataType>string</DataType>
      </ClaimType>
      <ClaimType Id="attribute_memberid">
        <DisplayName>Member ID</DisplayName>
        <DataType>string</DataType>
      </ClaimType>
      <ClaimType Id="nameId">
        <DisplayName>NameID</DisplayName>
        <DataType>string</DataType>
      </ClaimType>
      <ClaimType Id="sp">
        <DisplayName>Service Provider (short)</DisplayName>
        <DataType>string</DataType>
      </ClaimType>

      <ClaimType Id="entityIdFromQuery">
        <DisplayName>EntityId from Query</DisplayName>
        <DataType>string</DataType>
      </ClaimType>

    </ClaimsSchema>
  </BuildingBlocks>
  <ClaimsProviders>
    <ClaimsProvider>
      <DisplayName>IdpSso - Querystring Reader</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="SelfAsserted-ReadQueryString">
          <DisplayName>Read QueryString and map to claims (non-UI)</DisplayName>

          <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.GenericProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> 

          <Metadata>
            <Item Key="EnableUserIntervention">false</Item>
            <Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>
            <Item Key="PersistClaims">true</Item>
          </Metadata>

          <InputClaims />

          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="enc_attrs_token" DefaultValue="{QueryString:enc_attrs_token}" AlwaysUseDefaultValue="true" />
            <OutputClaim ClaimTypeReferenceId="serviceProviderCode" DefaultValue="{QueryString:sp}" AlwaysUseDefaultValue="true" />
          </OutputClaims>

          <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
        </TechnicalProfile>

      </TechnicalProfiles>
    </ClaimsProvider>
    <ClaimsProvider>
      <DisplayName>IdpSso - Backend Connector</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="REST-GetAssertionAttributes-UAT">
          <DisplayName>Call backend for assertion attributes</DisplayName>

          <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine" />

          <Metadata>
            <Item Key="ServiceUrl">https://***.azurefd.net/idp/sso/assertion-attributes?subscription-key=***</Item>
            <Item Key="SendClaimsIn">Body</Item>
            <Item Key="AuthenticationType">None</Item>
            <Item Key="HttpBinding">POST</Item>
            <Item Key="Content-Type">application/json</Item>
            <Item Key="HttpResponseTimeoutInSeconds">30</Item>
            <Item Key="IncludeClaimResolvingInClaimsHandling">true</Item>
            <Item Key="AllowInsecureAuthInProduction">true</Item>
          </Metadata>

          <InputClaims>
            <InputClaim ClaimTypeReferenceId="enc_attrs_token" PartnerClaimType="encryptedToken" DefaultValue="{QueryString:enc_attrs_token}" AlwaysUseDefaultValue="true" />
            <InputClaim ClaimTypeReferenceId="serviceProviderCode" PartnerClaimType="serviceProviderCode" DefaultValue="{QueryString:sp}" AlwaysUseDefaultValue="true" />
            <InputClaim ClaimTypeReferenceId="entityIdFromQuery" PartnerClaimType="entityIdFromQuery" DefaultValue="{QueryString:EntityId}" AlwaysUseDefaultValue="true" />
          </InputClaims>

          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="attribute_firstname" PartnerClaimType="attribute_firstname" />
            <OutputClaim ClaimTypeReferenceId="attribute_lastname" PartnerClaimType="attribute_lastname" />
            <OutputClaim ClaimTypeReferenceId="attribute_birthdate" PartnerClaimType="attribute_birthdate" />
            <OutputClaim ClaimTypeReferenceId="attribute_gender" PartnerClaimType="attribute_gender" />
            <OutputClaim ClaimTypeReferenceId="attribute_memberid" PartnerClaimType="attribute_memberid" />
            <OutputClaim ClaimTypeReferenceId="nameId" PartnerClaimType="nameId" />
          </OutputClaims>

          <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
        </TechnicalProfile>

      </TechnicalProfiles>
    </ClaimsProvider>
    <ClaimsProvider>
      <DisplayName>IdP SAML Issuer</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="Saml2Assertion-TP">
          <DisplayName>SAML2 Assertion</DisplayName>
          <Protocol Name="SAML2" />
          <Metadata>
            <Item Key="IdpInitiatedProfileEnabled">true</Item>
            <Item Key="Issuer">https://***.b2clogin.com/***/B2C_1A_IDPSSO</Item>
            <Item Key="PartnerEntityId">***ENTITY***</Item>
            <Item Key="PartnerEndpoint">https://***.example.com/saml/SSO</Item>
            <Item Key="Audience">***ENTITY***</Item>
            <Item Key="SignSamlMessage">true</Item>
            <Item Key="SamlEncryptAssertion">true</Item>
            <Item Key="EncryptClaims">true</Item>
            <Item Key="NameIdFormat">urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</Item>
            <Item Key="AssertionLifetime">240</Item>
          </Metadata>
          <CryptographicKeys>
            <Key Id="SamlIssuerSigningKey" StorageReferenceId="B2C_1A_***" />
            <Key Id="issuer_secret" StorageReferenceId="B2C_1A_***" />
            <Key Id="SamlAssertionSigning" StorageReferenceId="B2C_1A_***" />
            <Key Id="SamlAssertionEncryptionKey" StorageReferenceId="B2C_1A_***" />
            <Key Id="SamlAssertionEncryption"   StorageReferenceId="B2C_1A_***" />
          </CryptographicKeys>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="attribute_firstname" />
            <InputClaim ClaimTypeReferenceId="attribute_lastname" />
            <InputClaim ClaimTypeReferenceId="attribute_birthdate" />
            <InputClaim ClaimTypeReferenceId="attribute_gender" />
            <InputClaim ClaimTypeReferenceId="attribute_memberid" />
            <InputClaim ClaimTypeReferenceId="nameId" />
          </InputClaims>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="attribute_firstname" PartnerClaimType="attribute_firstname" />
            <OutputClaim ClaimTypeReferenceId="attribute_lastname" PartnerClaimType="attribute_lastname" />
            <OutputClaim ClaimTypeReferenceId="attribute_birthdate" PartnerClaimType="attribute_birthdate" />
            <OutputClaim ClaimTypeReferenceId="attribute_gender" PartnerClaimType="attribute_gender" />
            <OutputClaim ClaimTypeReferenceId="attribute_memberid" PartnerClaimType="attribute_memberid" />
            <OutputClaim ClaimTypeReferenceId="nameId" PartnerClaimType="NameID" />
          </OutputClaims>
          <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>
  </ClaimsProviders>
  <UserJourneys>
    <UserJourney Id="SignSamlAssertionForTP">
      <OrchestrationSteps>

        <!-- STEP 1: read querystring into claims bag 
        <OrchestrationStep Order="1" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="ReadQueryString" TechnicalProfileReferenceId="SelfAsserted-ReadQueryString" />
          </ClaimsExchanges>
        </OrchestrationStep>
        -->

        <OrchestrationStep Order="1" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="GetAttributes" TechnicalProfileReferenceId="REST-GetAssertionAttributes-UAT" />
          </ClaimsExchanges>
        </OrchestrationStep>

        <OrchestrationStep Order="2" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="Saml2Assertion-TP" />
      </OrchestrationSteps>
      <ClientDefinition ReferenceId="DefaultWeb" />
    </UserJourney>
  </UserJourneys>
</TrustFrameworkPolicy>

1 Answer 1

1

Where did you find that {QueryString:}? It's not mentioned in the docs: https://learn.microsoft.com/en-us/azure/active-directory-b2c/claim-resolver-overview#oauth2-key-value-parameters.

You need to use {OAUTH-KV:any custom query string} instead.

E.g. {QueryString:EntityId} should be {OAUTH-KV:EntityId}.

Or if this is in fact a SAML policy, then you need SAML-KV instead: https://learn.microsoft.com/en-us/azure/active-directory-b2c/claim-resolver-overview#saml-key-value-parameters.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you @juunas, use of OAUTH-KV indeed resolved issue

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.