azure-ad-b2cazure-ad-b2c-custom-policyidentity-experience-framework

Azure B2C - The provided token does not contain a valid issuer


I'm using Azure B2C to connect to an external OpenID Connect identity provider, i created a basic user flow within B2C which works but only brings back a small number of claims so i need to create a custom policy to pass custom input parameters to my IDP and collect additional claims.

I started with the SocialAndLocalAccount sample and modified with my IDP's details:

TrustFrameworkBase.xml (Cut down to Technical Profile + User Journey)

      ...
      <ClaimType Id="sub">
        <DisplayName>Subject</DisplayName>
        <DataType>string</DataType>
        <DefaultPartnerClaimTypes>
          <Protocol Name="OpenIdConnect" PartnerClaimType="sub" />
        </DefaultPartnerClaimTypes>
        <UserHelpText />
      </ClaimType>
      <ClaimType Id="ui_locales">
        <DisplayName>UI Locales</DisplayName>
        <DataType>string</DataType>
        <UserHelpText>Special parameter passed for account authentication to Tell Us Once.</UserHelpText>
      </ClaimType>
      <ClaimType Id="givenName">
        <DisplayName>Given Name</DisplayName>
        <DataType>string</DataType>
        <DefaultPartnerClaimTypes>
          <Protocol Name="OAuth2" PartnerClaimType="given_name" />
          <Protocol Name="OpenIdConnect" PartnerClaimType="given_name" />
          <Protocol Name="SAML2" PartnerClaimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname" />
        </DefaultPartnerClaimTypes>
        <UserHelpText>Your given name (also known as first name).</UserHelpText>
        <UserInputType>TextBox</UserInputType>
      </ClaimType>
      <ClaimType Id="surname">
        <DisplayName>Surname</DisplayName>
        <DataType>string</DataType>
        <DefaultPartnerClaimTypes>
          <Protocol Name="OAuth2" PartnerClaimType="family_name" />
          <Protocol Name="OpenIdConnect" PartnerClaimType="family_name" />
          <Protocol Name="SAML2" PartnerClaimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname" />
        </DefaultPartnerClaimTypes>
        <UserHelpText>Your surname (also known as family name or last name).</UserHelpText>
        <UserInputType>TextBox</UserInputType>
      </ClaimType>
      <ClaimType Id="DateOfBirth">
        <DisplayName>Date Of Birth</DisplayName>
        <DataType>string</DataType>
        <DefaultPartnerClaimTypes>
          <Protocol Name="OAuth2" PartnerClaimType="birthdate" />
          <Protocol Name="OpenIdConnect" PartnerClaimType="birthdate" />
          <Protocol Name="SAML2" PartnerClaimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/dateofbirth" />
        </DefaultPartnerClaimTypes>
        <UserHelpText>Your date of birth.</UserHelpText>
        <UserInputType>TextBox</UserInputType>
      </ClaimType>
      ...
    <ClaimsProvider>
      <Domain>tellusonce</Domain>
      <DisplayName>Tell Us Once</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="TUO-OpenIdConnect">
          <DisplayName>Tell Us Once Login</DisplayName>
          <Description>Login through Tell Us Once</Description>
          <Protocol Name="OpenIdConnect" />
          <Metadata>
            <Item Key="client_id">this is where i put my client id</Item>
            <Item Key="ProviderName">Tell Us Once</Item>
            <Item Key="METADATA">this is where my metadata is</Item>
            <Item Key="response_types">code</Item>
            <Item Key="response_mode">form_post</Item>
            <Item Key="scope">openid</Item>
            <Item Key="HttpBinding">POST</Item>
            <Item Key="UsePolicyInRedirectUri">false</Item>
          </Metadata>
          <CryptographicKeys>
            <Key Id="client_secret" StorageReferenceId="B2C_1A_TellUsOnceSecret" />
          </CryptographicKeys>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="scope" DefaultValue="openid profile" />
            <InputClaim ClaimTypeReferenceId="prompt" DefaultValue="login" />
            <InputClaim ClaimTypeReferenceId="ui_locales" DefaultValue="en-US" />
          </InputClaims>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="issuerUserId" PartnerClaimType="sub" />
            <OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />
            <OutputClaim ClaimTypeReferenceId="middleName" DefaultValue="" />
            <OutputClaim ClaimTypeReferenceId="surname" PartnerClaimType="family_name" />
            <OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
            <OutputClaim ClaimTypeReferenceId="DateOfBirth" PartnerClaimType="birthdate" />
            <OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="ITP-B2C" />
            <OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="ITP-Auth-DEV-OIDC" />
          </OutputClaims>
          <OutputClaimsTransformations>
            <OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName" />
            <OutputClaimsTransformation ReferenceId="CreateUserPrincipalName" />
            <OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId" />
            <OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromAlternativeSecurityId" />
          </OutputClaimsTransformations>
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>
    ...
  <UserJourneys>
    <UserJourney Id="SignUpOrSignInOidc">
      <OrchestrationSteps>
        <OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
          <ClaimsProviderSelections>
            <ClaimsProviderSelection TargetClaimsExchangeId="TUO-OIDCExchange" />
          </ClaimsProviderSelections>
        </OrchestrationStep>
        <OrchestrationStep Order="2" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="TUO-OIDCExchange" TechnicalProfileReferenceId="TUO-OpenIdConnect" />
          </ClaimsExchanges>
        </OrchestrationStep>
        <!-- For social IDP authentication, attempt to find the user account in the directory. -->
        <OrchestrationStep Order="3" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="AADUserReadUsingAlternativeSecurityId" TechnicalProfileReferenceId="AAD-UserReadUsingAlternativeSecurityId-NoError" />
          </ClaimsExchanges>
        </OrchestrationStep>
        <!-- Create the user in the directory if one does not already exist. -->
        <OrchestrationStep Order="4" Type="ClaimsExchange">
          <Preconditions>
            <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
              <Value>objectId</Value>
              <Action>SkipThisOrchestrationStep</Action>
            </Precondition>
          </Preconditions>
          <ClaimsExchanges>
            <ClaimsExchange Id="AADUserWrite" TechnicalProfileReferenceId="AAD-UserWriteUsingAlternativeSecurityId" />
          </ClaimsExchanges>
        </OrchestrationStep>
        <OrchestrationStep Order="5" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
      </OrchestrationSteps>
      <ClientDefinition ReferenceId="DefaultWeb" />
    </UserJourney>
  </UserJourneys>

TrustFrameworkExtensions.xml

<TrustFrameworkPolicy 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06" 
  PolicySchemaVersion="0.3.0.0" 
  TenantId="itpauthdev.onmicrosoft.com" 
  PolicyId="B2C_1A_TrustFrameworkExtensions" 
  PublicPolicyUri="http://itpauthdev.onmicrosoft.com/B2C_1A_TrustFrameworkExtensions" 
  TenantObjectId="ae2201eb-e4e9-44e7-8c73-b52e37ba01f8">
  <BasePolicy>
    <TenantId>itpauthdev.onmicrosoft.com</TenantId>
    <PolicyId>B2C_1A_TrustFrameworkBase</PolicyId>
  </BasePolicy>
  <BuildingBlocks></BuildingBlocks>
  <ClaimsProviders>
    <ClaimsProvider>
      <Domain>tellusonce</Domain>
      <DisplayName>Tell Us Once</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="TUO-OpenIdConnect">
          <DisplayName>Tell Us Once Login</DisplayName>
          <Description>Login through Tell Us Once</Description>
          <Protocol Name="OpenIdConnect" />
          <Metadata>
            <Item Key="client_id">this is where i put my client id</Item>
            <Item Key="ProviderName">Tell Us Once</Item>
            <Item Key="METADATA">this is where my metadata is</Item>
            <Item Key="response_types">code</Item>
            <Item Key="response_mode">form_post</Item>
            <Item Key="scope">openid</Item>
            <Item Key="HttpBinding">POST</Item>
            <Item Key="UsePolicyInRedirectUri">false</Item>
          </Metadata>
          <CryptographicKeys>
            <Key Id="client_secret" StorageReferenceId="B2C_1A_TellUsOnceSecret" />
          </CryptographicKeys>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="acr_values" DefaultValue="urn:id.gov.au:tdif:acr:ip1:cl1" />
            <InputClaim ClaimTypeReferenceId="scope" DefaultValue="openid profile" />
            <InputClaim ClaimTypeReferenceId="prompt" DefaultValue="login" />
            <InputClaim ClaimTypeReferenceId="ui_locales" DefaultValue="en-US" />
          </InputClaims>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="issuerUserId" PartnerClaimType="sub" />
            <OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />
            <OutputClaim ClaimTypeReferenceId="middleName" DefaultValue="" />
            <OutputClaim ClaimTypeReferenceId="surname" PartnerClaimType="family_name" />
            <OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
            <OutputClaim ClaimTypeReferenceId="DateOfBirth" PartnerClaimType="birthdate" />
            <OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="ITP-B2C" />
            <OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="ITP-Auth-DEV-OIDC" />
          </OutputClaims>
          <OutputClaimsTransformations>
            <OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName" />
            <OutputClaimsTransformation ReferenceId="CreateUserPrincipalName" />
            <OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId" />
            <OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromAlternativeSecurityId" />
          </OutputClaimsTransformations>
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>
  </ClaimsProviders>
  <UserJourneys>
    <UserJourney Id="SignUpOrSignInOidc">
      <OrchestrationSteps>
        <OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
          <ClaimsProviderSelections>
            <ClaimsProviderSelection TargetClaimsExchangeId="TUO-OIDCExchange" />
          </ClaimsProviderSelections>
        </OrchestrationStep>
        <OrchestrationStep Order="2" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="TUO-OIDCExchange" TechnicalProfileReferenceId="TUO-OpenIdConnect" />
          </ClaimsExchanges>
        </OrchestrationStep>
        <!-- For social IDP authentication, attempt to find the user account in the directory. -->
        <OrchestrationStep Order="3" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="AADUserReadUsingAlternativeSecurityId" TechnicalProfileReferenceId="AAD-UserReadUsingAlternativeSecurityId-NoError" />
          </ClaimsExchanges>
        </OrchestrationStep>
        <!-- Create the user in the directory if one does not already exist. -->
        <OrchestrationStep Order="4" Type="ClaimsExchange">
          <Preconditions>
            <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
              <Value>objectId</Value>
              <Action>SkipThisOrchestrationStep</Action>
            </Precondition>
          </Preconditions>
          <ClaimsExchanges>
            <ClaimsExchange Id="AADUserWrite" TechnicalProfileReferenceId="AAD-UserWriteUsingAlternativeSecurityId" />
          </ClaimsExchanges>
        </OrchestrationStep>
        <OrchestrationStep Order="5" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
      </OrchestrationSteps>
      <ClientDefinition ReferenceId="DefaultWeb" />
    </UserJourney>
  </UserJourneys>
</TrustFrameworkPolicy>

signin_signup_oidc.xml

<TrustFrameworkPolicy 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06" 
  PolicySchemaVersion="0.3.0.0" 
  TenantId="itpauthdev.onmicrosoft.com" 
  PolicyId="B2C_1A_signup_signin_oidc" 
  PublicPolicyUri="http://itpauthdev.onmicrosoft.com/B2C_1A_signup_signin" 
  TenantObjectId="ae2201eb-e4e9-44e7-8c73-b52e37ba01f8">
  <BasePolicy>
    <TenantId>itpauthdev.onmicrosoft.com</TenantId>
    <PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId>
  </BasePolicy>
  <RelyingParty>
    <DefaultUserJourney ReferenceId="SignUpOrSignInOidc" />
    <UserJourneyBehaviors></UserJourneyBehaviors>
    <TechnicalProfile Id="PolicyProfile">
      <DisplayName>PolicyProfile</DisplayName>
      <Protocol Name="OpenIdConnect" />
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="givenName" />
        <OutputClaim ClaimTypeReferenceId="middleName" DefaultValue="" />
        <OutputClaim ClaimTypeReferenceId="surname" />
        <OutputClaim ClaimTypeReferenceId="email" />
        <OutputClaim ClaimTypeReferenceId="DateOfBirth" DefaultValue="ClaimNotFound" />
        <OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub" />
        <OutputClaim ClaimTypeReferenceId="identityProvider" />
        <OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />
      </OutputClaims>
      <SubjectNamingInfo ClaimType="sub" />
    </TechnicalProfile>
  </RelyingParty>
</TrustFrameworkPolicy>

When i click "B2C_1A_signin_signup_oidc" and choose Run User Flow i get my IDP's login screen, once i've successfully logged in i get an authorization code response from my IDP, my IDP has been configured with the "/authresp" redirect uri as per Microsoft documentation ("Redirect Uri" section at the bottom) but then it doesn't seem like "/authresp" is exchanging the code for an id_token because when it redirects to jwt.ms i get "token does not contain a valid issuer".

Fiddler Code Response

By using fiddler's inspector i can see that the IDP's endpoint is responding with a code that looks something like this (not actual code):

<HTML>

<HEAD>
    <TITLE>OIDC Form_Post Response</TITLE>
</HEAD>

<BODY Onload="document.forms[0].submit()">
    <FORM METHOD="POST" ACTION="https://itpauthdev.b2clogin.com/itpauthdev.onmicrosoft.com/oauth2/authresp"> <INPUT
            TYPE="HIDDEN" NAME="code"
            VALUE="4cfed1f1-bfcc-42db-b39c-d79480f6d333.056c6e05-eaec-426a-a5e7-bf9f66f962be.15ac212d-38b7-4e9b-80e2-a948be9360e5" />
        <INPUT TYPE="HIDDEN" NAME="state"
            VALUE="StateProperties=eyJTSUQiOiJ4LW1zLWNwaW0tcmM6M2RkYmRhYjktY2VkZS00MDA4LTliNWYtOGRmZjU2ZDZmZDYzIiveWIlEIjoiMmFtkoc5YjUtMzg0Zi00M2JmLThkMDUtMjIzMGYxNzU1M2JjIiwiVE9CRJI6ImFlMjIwMWViLWU0ZTktNDRlNy04YzczLWI1MmUzN2JhMDFmOCJ9" />
        <INPUT TYPE="HIDDEN" NAME="session_state" VALUE="65e01676-90d1-4c5e-a7f4-66dfd7b3211b" /> <NOSCRIPT>
            <P>JavaScript is disabled. We strongly recommend to enable it. Click the button below to continue .</P>
            <INPUT name="continue" TYPE="SUBMIT" VALUE="CONTINUE" />
        </NOSCRIPT> </FORM>
</BODY>

</HTML>

Fiddler AuthResp Response

Using Fiddler once again, i can see that AuthResp does not exchange the code for an id_token using the IDP's token endpoint (or encounters an error, i don't get told anything helpful). Instead i get back:

<html>

<head>
    <title>Object moved</title>
</head>

<body>
    <h2>Object moved to <a
            href="https://jwt.ms/#error=invalid_request&amp;error_description=AADB2C90238%3a+The+provided+token+does+not+contain+a+valid+issuer.+Please+provide+another+token+and+try+again.%0d%0aCorrelation+ID%3a+2ad979b5-384f-43bf-8d05-2230f17553bc%0d%0aTimestamp%3a+2021-02-26+04%3a29%3a08Z%0d%0a">here</a>.
    </h2>
</body>

</html>

Questions

  1. I read in documentation somewhere that an "OpenIDConnect" technical profile will automatically exchange the code for an id token but i can't seem to find the document, is my configuration missing a step or do i have to do this manually with an extra userjourney?
  2. If there is miscommunication between "/authresp" and the token endpoint, is there somewhere i can look to get a more descriptive error message? Both jwt.ms and the audit logs just say "Token does not contain a valid issuer".

JWTError


Solution

  • The error means the id token from the OIDC provider has an issuer (iss) claim that doesn’t match the issuer in the well known OIDC config endpoint. You can add an issuer element to the metadata of TUO-OpenIdConnect technical profile to override it with the value that appears in the token.

    https://learn.microsoft.com/en-us/azure/active-directory-b2c/openid-connect-technical-profile#metadata

    issuer : The unique identifier of an OpenID Connect identity provider. The value of issuer metadata takes precedence over the issuer specified in the OpenID well-known configuration endpoint. If specified, Azure AD B2C checks whether the iss claim in a token returned by the identity provider is equal to the one specified in the issuer metadata.