azure-ad-b2c / samples

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

AADB2C90233: The provided id_token_hint parameter failed signature validation. #261

Closed DCurtin closed 3 years ago

DCurtin commented 3 years ago

Guides I was following https://techcommunity.microsoft.com/t5/azure-developer-community-blog/magic-signin-amp-invitation-mails-in-azure-ad-b2c/ba-p/1198337 and https://github.com/azure-ad-b2c/samples/tree/master/policies/sign-in-with-magic-link

there was another one as well but I can't find it.

I'm generating the JWT from a node server, at one point I noticed I was using ms instead of seconds for nbf/exp after I changed that I started getting a different error code about audience not matching or something. Then after pushing the policy up again (B2C_1A_SIGNIN_WITH_MAGIC_LINK) it started giving me AADB2C90233 again and i'm not sure at this point what the issue is.

is iat required? is iss required? should iss match issuer from https://testinvestmentsponsorportal.b2clogin.com/testinvestmentsponsorportal.onmicrosoft.com/B2C_1A_OIDC/v2.0/.well-known/openid-configuration

I noticed issuer and IdTokenAudience are optional in the policy but https://docs.microsoft.com/en-us/azure/active-directory-b2c/id-token-hint states that aud and iss are required in the hint.

Is there anyway to further debug the issue to find out what exactly is failing? I was trying ApplicationInsights but didn't find the output logs terribly useful, though I definitely could have been overlooking something.

Any assistance would be appreciated.

From B2C_1A_SIGNIN_WITH_MAGIC_LINK

<OrchestrationStep Order="1" Type="GetClaims" CpimIssuerTechnicalProfileReferenceId="IdTokenHint_ExtractClaims" />

...

    <ClaimsProvider>
      <DisplayName>My ID Token Hint ClaimsProvider</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="IdTokenHint_ExtractClaims">
          <DisplayName> My ID Token Hint TechnicalProfile</DisplayName>
          <Protocol Name="None" />
          <Metadata>
            <Item Key="METADATA">https://testinvestmentsponsorportal.b2clogin.com/testinvestmentsponsorportal.onmicrosoft.com/B2C_1A_OIDC/v2.0/.well-known/openid-configuration</Item>
          </Metadata>
        <OutputClaims>
          <OutputClaim ClaimTypeReferenceId="email" />  
        </OutputClaims>
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>

I also have another policy B2C_1A_OIDC that hosts the well-known, following the guide from the first link to set those up.

jwk-uri from above well-known https://testinvestmentsponsorportal.b2clogin.com/testinvestmentsponsorportal.onmicrosoft.com/b2c_1a_oidc/discovery/v2.0/keys Ignore the second key, I added that when testing something else, the first key is the one related to the secret that I'm using for signing. This key originated from a self-signed key generated in powershell kid=>5E9A83B7800B761E1718A156103B712456C3D92C

{
  "keys": [
    {"kid":"5E9A83B7800B761E1718A156103B712456C3D92C",
    "exp":1658525706,"nbf":1626989106,
    "x5c": ["MIIDRjCCAi6gAwIBAgIQGr68P0U/JqJN1v7gm9JF3zANBgkqhkiG9w0BAQsFADA2MTQwMgYDVQQDDCt0ZXN0aW52ZXN0bWVudHNwb25zb3Jwb3J0YWwub25taWNyb3NvZnQuY29tMB4XDTIxMDcyMjIxMjUwNloXDTIyMDcyMjIxMzUwNlowNjE0MDIGA1UEAwwrdGVzdGludmVzdG1lbnRzcG9uc29ycG9ydGFsLm9ubWljcm9zb2Z0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMRGB1SPjk/LOwOfNAPl2C39TZEMnw9vhAenjxErBz5q/gXIa2Z6hy4yTEVOts6BO7QVkzaFzjPB/y8FugVUAhkqoTYWClcmPPfx2WRRk088+tFEbJfv7Iym7U5DzD5qUqFJq2JHVHjkM4lEvZy6pn5rHTSAwGZGbLZkXt06nG3Z6WgPeUpg/Nc7Z07Sr3yP98QNW21+LWKZy9PXc7kwIxlD/V+P0MGUjyjOAYNTtZfBQh3hSjy+eCL4Nbgo1qJMQ9UIq1fSokKFkw7HsdYSkIdo7PLX1TPA6DINUXlkjYrASnAccoqYvjrqEB/6N6WSEt5LkRB/+OaDEB9NI+V0FRUCAwEAAaNQME4wDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAdBgNVHQ4EFgQUEcglYxwnRjRV3QiApMWJmOFBxCEwDQYJKoZIhvcNAQELBQADggEBALjZ9Qglt062aPzbOF3QHVyIis3ppYnJGzAOfFnPnMxq68hLH0mIe94lAmqWw3Rcecsex/EHLoK5Hre86a1lC06H7tDbMU7PGUdTyffzoajvNRLZp91mUO6xI3YJgWAa3Lo9uYiZzCPlyFdzyPUd63RVJTdmolZsm2PmiP244N9b2JTn6f8ij1kOlqdkEx49aXS/NQabgwMTQ21ylJUKih1OilevPy/sbJGLvxYzUMA93+4pycJe1MSlbOJpuPDft/t9/mO2iPi6XiZ6jAh5aIWC8hIDLJJcurz2eNtgola3Iazl0VGi/v2e205XoQFX7vGPw7KymNu1B6UJtkrdKhI="],
    "x5t":"XpqDt4ALdh4XGKFWEDtxJFbD2Sw",
    "kty":"RSA",
    "e":"AQAB",
    "n":"xEYHVI-OT8s7A580A-XYLf1NkQyfD2-EB6ePESsHPmr-BchrZnqHLjJMRU62zoE7tBWTNoXOM8H_LwW6BVQCGSqhNhYKVyY89_HZZFGTTzz60URsl-_sjKbtTkPMPmpSoUmrYkdUeOQziUS9nLqmfmsdNIDAZkZstmRe3TqcbdnpaA95SmD81ztnTtKvfI_3xA1bbX4tYpnL09dzuTAjGUP9X4_QwZSPKM4Bg1O1l8FCHeFKPL54Ivg1uCjWokxD1QirV9KiQoWTDsex1hKQh2js8tfVM8DoMg1ReWSNisBKcBxyipi-OuoQH_o3pZIS3kuREH_45oMQH00j5XQVFQ"},
    {"kid":"aknB05mtSCxTh378XGLeWSKdCuKemSm2VL4OoLkp1gE",
    "kty":"RSA",
    "e":"AQAB",
    "n":"pnC8rs55tS-h7zJLj0oEDzXMjvMVqOQYC4M6E-M-MMo8oAtnpcGUjlxnE-1MxqmrN-wHteLIaB_8awOTnn89_DyWW0mQF035Vb6fWHz7u_asV-FngvdGsIHzsuggTLg37pvaUlNQlh7yiH_61cIdyfqSs4EiscKjfrNStVdkwAIaHBa0y4s_n2f1f1DgDydsZfW9aobYDMEtEqEFXe7xw9gF117I_nTZXkPSbRCnvQPfL9GBzOT5xrkuRqNqlOz38PsEMSHC5lwCgqaTZKprdYdO8B1namz1PaomWTHMe9fJLZowQP05Z8mcfPAVnM4on60icxSeAPgpwcCp2_kDeQ"}
  ]
}

powershell command

>> -KeyExportPolicy Exportable `
>> -Subject "CN=testApp.testinvestmentsponsorportal.onmicrosoft.com" `
>> -KeyAlgorithm RSA `
>> -KeyLength 2048 `
>> -KeyUsage DigitalSignature `
>> -NotAfter (Get-Date).AddMonths(12) `
>> -CertStoreLocation "Cert:\CurrentUser\My"

server-side generated hint

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjVFOUE4M0I3ODAwQjc2MUUxNzE4QTE1NjEwM0I3MTI0NTZDM0Q5MkMiLCJ4NXQiOiJYcHFEdDRBTGRoNFhHS0ZXRUR0eEpGYkQyU3cifQ.eyJlbWFpbCI6InRlc3RtYWlsQGdtYWlsLmNvbSIsIm5iZiI6MTYyNzEzNDI5MiwiZXhwIjoxNjI3MzA3MDkyLCJpc3MiOiJodHRwczovL3Rlc3RpbnZlc3RtZW50c3BvbnNvcnBvcnRhbC5iMmNsb2dpbi5jb20vdjIuMC8ifQ.GQWxQXlXJvZdqMXf9vcmQnhLLjdm6yapeiPBETrhR8qv5N6bY5FzF5uTGzSneNepu0Sdlul8g68lF8RuSSb5XK8ROFqyyRLAbEFm86-DqWhAVkwwvQGiCV-3ga3ZeNKHnrvJYpiOm-CCq-AX23_JtHG7_hmg9a818JJIwt2xDdw2T-lAS1IM94HLQqgfjbKzhBulXvo3YZLsxIiKxYiKqfB6M5C_NqydRn5Te4WzfPabn1BKwdPBL_hoHAZrU3aHbEZtldePyj8NHrW5lS2ClMiX8ClYHdk1InkgX-9EfZSkowNwedVGeG-eq4CeHvJpdRqb5KyncYB1mlPN3O-31Q

B2C_1A_signin_with_magic_link Full Policy

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<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="testinvestmentsponsorportal.onmicrosoft.com"
  PolicyId="B2C_1A_signin_with_magic_link"
  PublicPolicyUri="http://testinvestmentsponsorportal.onmicrosoft.com/B2C_1A_signin_with_magic_link"
  >
  <!--
  DeploymentMode="Development"
  UserJourneyRecorderEndpoint="urn:journeyrecorder:applicationinsights"
  -->

  <BasePolicy>
    <TenantId>testinvestmentsponsorportal.onmicrosoft.com</TenantId>
    <PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId>
  </BasePolicy>

   <BuildingBlocks>
    <ClaimsSchema>
      <!--Sample: Stores the error message for unsolicited request (a request without id_token_hint) and user not found-->
      <ClaimType Id="errorMessage">
          <DisplayName>Error</DisplayName>
          <DataType>string</DataType>
        <UserHelpText>Add help text here</UserHelpText>
          <UserInputType>Paragraph</UserInputType>
      </ClaimType>
    </ClaimsSchema>

    <ClaimsTransformations>
      <!--Sample: Initiates the errorMessage claims type with the error message-->
      <ClaimsTransformation Id="CreateUnsolicitedErrorMessage" TransformationMethod="CreateStringClaim">
        <InputParameters>
          <InputParameter Id="value" DataType="string" Value="You cannot sign-in without invitation" />
        </InputParameters>
        <OutputClaims>
          <OutputClaim ClaimTypeReferenceId="errorMessage" TransformationClaimType="createdClaim" />
        </OutputClaims>
      </ClaimsTransformation>

      <!--Sample: Initiates the errorMessage claims type with the error message user not found-->
      <ClaimsTransformation Id="CreateUserNotFoundErrorMessage" TransformationMethod="CreateStringClaim">
        <InputParameters>
          <InputParameter Id="value" DataType="string" Value="You aren't registered in the system!" />
        </InputParameters>
        <OutputClaims>
          <OutputClaim ClaimTypeReferenceId="errorMessage" TransformationClaimType="createdClaim" />
        </OutputClaims>
      </ClaimsTransformation>
    </ClaimsTransformations>
  </BuildingBlocks>

  <ClaimsProviders>
    <!--Sample: This technical profile specifies how B2C should validate your token, and what claims you want B2C to extract from the token. 
      The METADATA value in the TechnicalProfile meta-data is required. 
      The “IdTokenAudience” and “issuer” arguments are optional (see later section)-->
    <ClaimsProvider>
      <DisplayName>My ID Token Hint ClaimsProvider</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="IdTokenHint_ExtractClaims">
          <DisplayName> My ID Token Hint TechnicalProfile</DisplayName>
          <Protocol Name="None" />
          <Metadata>
            <Item Key="METADATA">https://testinvestmentsponsorportal.b2clogin.com/testinvestmentsponsorportal.onmicrosoft.com/B2C_1A_OIDC/v2.0/.well-known/openid-configuration</Item>
          </Metadata>
        <OutputClaims>
          <OutputClaim ClaimTypeReferenceId="email" />  
        </OutputClaims>
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>

    <ClaimsProvider>
      <DisplayName>Self Asserted</DisplayName>
      <TechnicalProfiles>
        <!-- Demo: Show error message-->
        <TechnicalProfile Id="SelfAsserted-Error">
          <DisplayName>Unsolicited error message</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>
            <!-- Sample: Remove the continue button-->
            <Item Key="setting.showContinueButton">false</Item>
         </Metadata>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="errorMessage"/>
          </InputClaims>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="errorMessage"/>
          </OutputClaims>
        </TechnicalProfile>

        <!-- Demo: Show unsolicited error message-->
        <TechnicalProfile Id="SelfAsserted-Unsolicited">
          <InputClaimsTransformations>
            <InputClaimsTransformation ReferenceId="CreateUnsolicitedErrorMessage" />
          </InputClaimsTransformations>
          <IncludeTechnicalProfile ReferenceId="SelfAsserted-Error" />
        </TechnicalProfile>

        <!-- Demo: Show user not found error message-->
        <TechnicalProfile Id="SelfAsserted-UserNotFound">
          <InputClaimsTransformations>
            <InputClaimsTransformation ReferenceId="CreateUserNotFoundErrorMessage" />
          </InputClaimsTransformations>
          <IncludeTechnicalProfile ReferenceId="SelfAsserted-Error" />
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>

    <ClaimsProvider>
      <DisplayName>Azure Active Directory</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="AAD-UserReadUsingEmailAddress">
          <Metadata>
            <!--Sample: don't raise error if user not found. We have an orchestration step to handle the error message-->
            <Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">false</Item>
          </Metadata>
          <OutputClaims>
            <!--Sample: add optional claims to read from the directory-->
            <OutputClaim ClaimTypeReferenceId="givenName"/>
            <OutputClaim ClaimTypeReferenceId="surname"/>
          </OutputClaims>
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>
  </ClaimsProviders>

  <UserJourneys>
    <UserJourney Id="SignUpOrSignInWithEmail">
      <OrchestrationSteps>

        <!--Sample: Read the input claims from the id_token_hint-->
        <OrchestrationStep Order="1" Type="GetClaims" CpimIssuerTechnicalProfileReferenceId="IdTokenHint_ExtractClaims" />

        <!-- Sample: Check if user tries to run the policy without invitation -->
        <OrchestrationStep Order="2" Type="ClaimsExchange">
         <Preconditions>
            <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
              <Value>email</Value>
              <Action>SkipThisOrchestrationStep</Action>
            </Precondition>
          </Preconditions>        
          <ClaimsExchanges>
            <ClaimsExchange Id="SelfAsserted-Unsolicited" TechnicalProfileReferenceId="SelfAsserted-Unsolicited" />
          </ClaimsExchanges>
        </OrchestrationStep>

        <!--Sample: Read the user properties from the directory-->
        <OrchestrationStep Order="3" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="AADUserReadUsingEmailAddress" TechnicalProfileReferenceId="AAD-UserReadUsingEmailAddress"/>
          </ClaimsExchanges>
        </OrchestrationStep>

        <!-- Sample: Check whether the user not existed in the directory -->
        <OrchestrationStep Order="4" Type="ClaimsExchange">
          <Preconditions>
            <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
              <Value>objectId</Value>
              <Action>SkipThisOrchestrationStep</Action>
            </Precondition>
          </Preconditions>
          <ClaimsExchanges>
            <ClaimsExchange Id="SelfAssertedUserNotFound" TechnicalProfileReferenceId="SelfAsserted-UserNotFound" />
          </ClaimsExchanges>
        </OrchestrationStep>

        <!--Sample: Issue an access token-->
        <OrchestrationStep Order="5" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer"/>

      </OrchestrationSteps>
      <ClientDefinition ReferenceId="DefaultWeb"/>
    </UserJourney>
  </UserJourneys>

  <RelyingParty>
    <DefaultUserJourney ReferenceId="SignUpOrSignInWithEmail" />
    <!--UserJourneyBehaviors>
      <JourneyInsights TelemetryEngine="ApplicationInsights" InstrumentationKey="xxxxx" DeveloperMode="true" ClientEnabled="true" ServerEnabled="true" TelemetryVersion="1.0.0" />
    </UserJourneyBehaviors-->
    <TechnicalProfile Id="PolicyProfile">
      <DisplayName>PolicyProfile</DisplayName>
      <Protocol Name="OpenIdConnect" />
      <!--Sample: Set the input claims to be read from the id_token_hint-->
      <InputClaims>
        <InputClaim ClaimTypeReferenceId="email" />  
      </InputClaims>
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="displayName" />
        <OutputClaim ClaimTypeReferenceId="givenName" />
        <OutputClaim ClaimTypeReferenceId="surname" />
        <OutputClaim ClaimTypeReferenceId="email" />
        <OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="sub"/>
        <OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}" />
      </OutputClaims>
      <SubjectNamingInfo ClaimType="sub" />
    </TechnicalProfile>
  </RelyingParty>
</TrustFrameworkPolicy>
DCurtin commented 3 years ago

Nevermind, I was doing something incredibly stupid.

In summary for anyone else running into this issue

The above setup works for having b2c host the well-known keys I used issuer from the well-known key => https://{tenant}.b2clogin.com/{tennantId}/v2.0/

To get around AADB2C90209: The provided id_token_hint parameter does not contain an accepted audience. I explicitly set an audience in my policy and matched that in the jwt I was generating.

woutervanranst commented 2 years ago

Hi @DCurtin, I am indeed hitting AADB2C90209. Can you elaborate a bit on the solution "I explicitly set an audience in my policy and matched that in the jwt I was generating" (eg. where are you setting what)?

anh-duc-le commented 1 year ago

After a day of checking every configuration possible, I finally found my own specific problem. I hope my info adds to the list of things to check for anyone else who's trying to resolve this.

I had the stupid issue of having the wrong basePolicy configured on my SignIn_With_Magic_Links.xml, it was still referring to B2C_1A_TrustFrameworkExtensions instead of my custom B2C_1A_TrustFrameworkExtensions_Magic_Links.

This caused a mismatch of signing certificate on my app side and validating certificate on the B2C side.

So make sure the basePolicy points to the correct custom policy.

  <BasePolicy>
    <TenantId>my-tenant.onmicrosoft.com</TenantId>
    <!-- 
    <PolicyId>B2C_1A_TrustFrameworkExtensions</PolicyId> 
    -->
    <PolicyId>B2C_1A_TrustFrameworkExtensions_Magic_Links</PolicyId>
  </BasePolicy>

The custom B2C_1A_TrustFrameworkExtensions_Magic_Links.xml overrides the CryptographicKeys.issuer_secret values with my uploaded selfsigned certificate

   <CryptographicKeys>
      <Key Id="issuer_secret" StorageReferenceId="B2C_1A_MagicLinkSelfsignedCertificate" />
   </CryptographicKeys>

Now I browse to the openid-configuration for my magic link policy at https://my-tenant.b2clogin.com/my-tenant.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1A_SIGNIN_WITH_MAGIC_LINKS.

From there I navigate to the jwks_uri property value "jwks_uri": "https://my-tenant.b2clogin.com/my-tenant.onmicrosoft.com/discovery/v2.0/keys?p=b2c_1a_signin_with_magic_links", to find the validation keys available at this endpoint.

The kid value should now match the kid value from your jwt id token hint.

{
  "keys": [
    {"kid":"__YOUR_CERT_THUMBNAIL__","exp":2299761174,"nbf":1668608574,"x5c":

My metadata looks like this


          <Metadata>
            <!--Sample action required: replace with your endpoint location -->
            <Item Key="METADATA">https://my-tenant.b2clogin.com/my-tenant.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1A_SIGNIN_WITH_MAGIC_LINKS</Item>
            <Item Key="IdTokenAudience">registered-app-guid</Item>
            <Item Key="issuer">https://my-tenant.b2clogin.com/my-tenant-guid/v2.0/</Item>
          </Metadata>