azure-ad-b2cazure-ad-b2c-custom-policysendgrid-api-v3

Custom policy to send OTP code via sendgrid gives exception about otpToVerify not being found but required


I'm trying to send the OTP code via sendgrid so that I can use my own html template. Right now im getting this error message from Application insights:

Exception Message:A partner claim with id 'otpToVerify' is not found however is required in technical profile 'VerifyOtp' in policy 'B2C_1A_signup_signin' of tenant 'CENSORED.onmicrosoft.com'., Exception Type:InvalidReferenceException, CorrelationID:88e2b7dd-8049-48e9-bd01-0bef2c2569e8

And this is a more detailed message:

[{"outerId":"0","message":"Exception Message:A partner claim with id 'otpToVerify' is not found however is required in technical profile 'VerifyOtp' in policy 'B2C_1A_signup_signin' of tenant 'CENSORED.onmicrosoft.com'., Exception Type:InvalidReferenceException, CorrelationID:88e2b7dd-8049-48e9-bd01-0bef2c2569e8","type":"System.Exception","id":"39671138"}]

This is my extensions xml file contents with some of the fields redacted:

<?xml version="1.0" encoding="utf-8" ?>
<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="yourtenant.onmicrosoft.com" 
  PolicyId="B2C_1A_TrustFrameworkExtensions" 
  PublicPolicyUri="http://yourtenant.onmicrosoft.com/B2C_1A_TrustFrameworkExtensions">

  <BasePolicy>
    <TenantId>yourtenant.onmicrosoft.com</TenantId>
    <PolicyId>B2C_1A_TrustFrameworkBase</PolicyId>
  </BasePolicy>
  <BuildingBlocks>
    <ClaimsSchema>
      <ClaimType Id="signInName">
        <DisplayName>Sign In Name</DisplayName>
        <DataType>string</DataType>
        <UserHelpText>The email address you use to sign in</UserHelpText>
        <UserInputType>TextBox</UserInputType>
      </ClaimType>

      <ClaimType Id="otp">
        <DisplayName>Secondary One-time password</DisplayName>
        <DataType>string</DataType>
      </ClaimType>
      <ClaimType Id="emailRequestBody">
        <DisplayName>SendGrid request body</DisplayName>
        <DataType>string</DataType>
      </ClaimType>
      <ClaimType Id="VerificationCode">
        <DisplayName>Secondary Verification Code</DisplayName>
        <DataType>string</DataType>
        <UserHelpText>Enter your email verification code</UserHelpText>
        <UserInputType>TextBox</UserInputType>
      </ClaimType>

      <ClaimType Id="event">
        <DisplayName>Event Name</DisplayName>
        <DataType>string</DataType>
      </ClaimType>

      <ClaimType Id="timestamp">
        <DisplayName>Timestamp</DisplayName>
        <DataType>dateTime</DataType>
      </ClaimType>

    </ClaimsSchema>

    <ClaimsTransformations> 
      <ClaimsTransformation Id="GenerateEmailRequestBody" TransformationMethod="GenerateJson">
        <InputClaims>
          <InputClaim ClaimTypeReferenceId="email" TransformationClaimType="personalizations.0.to.0.email" />
          <InputClaim ClaimTypeReferenceId="otp" TransformationClaimType="personalizations.0.dynamic_template_data.otp" />
          <InputClaim ClaimTypeReferenceId="email" TransformationClaimType="personalizations.0.dynamic_template_data.email" />
        </InputClaims>
        <InputParameters>
          <InputParameter Id="template_id" DataType="string" Value="your_template_id"/>
          <InputParameter Id="from.email" DataType="string" Value="your_email@example.com"/>
          <InputParameter Id="personalizations.0.dynamic_template_data.subject" DataType="string" Value="Your organization email verification code"/>
        </InputParameters>
        <OutputClaims>
          <OutputClaim ClaimTypeReferenceId="emailRequestBody" TransformationClaimType="outputClaim"/>
        </OutputClaims>
      </ClaimsTransformation>
    </ClaimsTransformations>

    <ContentDefinitions>
      <ContentDefinition Id="api.localaccountsignup">
        <DataUri>urn:com:microsoft:aad:b2c:elements:contract:selfasserted:2.1.2</DataUri>
      </ContentDefinition>
      <ContentDefinition Id="api.localaccountpasswordreset">
        <DataUri>urn:com:microsoft:aad:b2c:elements:contract:selfasserted:2.1.2</DataUri>
      </ContentDefinition>
    </ContentDefinitions>

    <DisplayControls>
      <DisplayControl Id="emailVerificationControl" UserInterfaceControlType="VerificationControl">
        <DisplayClaims>
          <DisplayClaim ClaimTypeReferenceId="email" Required="true" />
          <DisplayClaim ClaimTypeReferenceId="VerificationCode" ControlClaimType="VerificationCode" Required="true" />
        </DisplayClaims>
        <OutputClaims>
          <OutputClaim ClaimTypeReferenceId="email" />
        </OutputClaims>
        <Actions>
          <Action Id="SendCode">
            <ValidationClaimsExchange>
              <ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="GenerateOtp" />
              <ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="SendOtp" />
            </ValidationClaimsExchange>
          </Action>
          <Action Id="VerifyCode">
            <ValidationClaimsExchange>
              <ValidationClaimsExchangeTechnicalProfile TechnicalProfileReferenceId="VerifyOtp" />
            </ValidationClaimsExchange>
          </Action>
        </Actions>
      </DisplayControl>
    </DisplayControls>

  </BuildingBlocks>

  <ClaimsProviders>
    <ClaimsProvider>
      <DisplayName>One time password technical profiles</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="GenerateOtp">
          <DisplayName>Generate one time password</DisplayName>
          <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.OneTimePasswordProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
          <Metadata>
            <Item Key="Operation">GenerateCode</Item>
            <Item Key="CodeExpirationInSeconds">600</Item> <!-- 10 minutes -->
            <Item Key="CodeLength">6</Item> <!-- OTP length -->
            <Item Key="CharacterSet">0-9</Item> <!-- Digits only -->
            <Item Key="NumRetryAttempts">5</Item>
            <Item Key="NumCodeGenerationAttempts">10</Item>
            <Item Key="ReuseSameCode">false</Item>
          </Metadata>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="identifier" />
          </InputClaims>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="otp" PartnerClaimType="otpGenerated" />
          </OutputClaims>
        </TechnicalProfile>

        <TechnicalProfile Id="VerifyOtp">
          <DisplayName>Verify one time password</DisplayName>
          <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.OneTimePasswordProtocolProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
          <Metadata>
            <Item Key="Operation">VerifyCode</Item>
          </Metadata>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="signInName" PartnerClaimType="identifier" />
            <InputClaim ClaimTypeReferenceId="otp" PartnerClaimType="otpToVerify" />
          </InputClaims>
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>

    <ClaimsProvider>
      <DisplayName>RestfulProvider</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="SendOtp">
          <DisplayName>Use SendGrid's email API to send the code to the user</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://api.sendgrid.com/v3/mail/send</Item>
            <Item Key="AuthenticationType">Bearer</Item>
            <Item Key="SendClaimsIn">Body</Item>
            <Item Key="ClaimUsedForRequestPayload">emailRequestBody</Item>
          </Metadata>
          <CryptographicKeys>
            <Key Id="BearerAuthenticationToken" StorageReferenceId="B2C_1A_SendGridSecret" /> <!-- SendGrid API Key -->
          </CryptographicKeys>
          <InputClaimsTransformations>
            <InputClaimsTransformation ReferenceId="GenerateEmailRequestBody" />
          </InputClaimsTransformations>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="emailRequestBody" />
          </InputClaims>
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>

    <ClaimsProvider>
      <DisplayName>Local Account</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="LocalAccountSignUpWithLogonEmail">
          <DisplayClaims>
            <DisplayClaim DisplayControlReferenceId="emailVerificationControl" />
            <DisplayClaim ClaimTypeReferenceId="displayName" Required="true" />
            <DisplayClaim ClaimTypeReferenceId="givenName" Required="true" />
            <DisplayClaim ClaimTypeReferenceId="surName" Required="true" />
            <DisplayClaim ClaimTypeReferenceId="newPassword" Required="true" />
            <DisplayClaim ClaimTypeReferenceId="reenterPassword" Required="true" />
          </DisplayClaims>
        </TechnicalProfile>

        <TechnicalProfile Id="LocalAccountDiscoveryUsingEmailAddress">
          <DisplayClaims>
            <DisplayClaim DisplayControlReferenceId="emailVerificationControl" />
          </DisplayClaims>
        </TechnicalProfile>

      </TechnicalProfiles>
    </ClaimsProvider>

    <ClaimsProvider>
      <DisplayName>Local Account SignIn</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="login-NonInteractive">
          <Metadata>
            <Item Key="client_id">your_client_id</Item>
            <Item Key="IdTokenAudience">your_token_audience</Item>
          </Metadata>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="client_id" DefaultValue="your_client_id" />
            <InputClaim ClaimTypeReferenceId="resource_id" PartnerClaimType="resource" DefaultValue="your_resource_id" />
          </InputClaims>
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>

  </ClaimsProviders>

  <UserJourneys>
      <UserJourney Id="CustomSignUpOrSignInWithOTP">
        <OrchestrationSteps>
            <OrchestrationStep Order="1" Type="CombinedSignInAndSignUp" ContentDefinitionReferenceId="api.signuporsignin">
              <ClaimsProviderSelections>
                  <ClaimsProviderSelection ValidationClaimsExchangeId="LocalAccountSigninEmailExchange" />
              </ClaimsProviderSelections>
              <ClaimsExchanges>
                  <ClaimsExchange Id="LocalAccountSigninEmailExchange" TechnicalProfileReferenceId="SelfAsserted-LocalAccountSignin-Email" />
              </ClaimsExchanges>
            </OrchestrationStep>

            <OrchestrationStep Order="2" Type="ClaimsExchange">
              <Preconditions>
                  <Precondition Type="ClaimsExist" ExecuteActionsIf="true">
                      <Value>objectId</Value>
                      <Action>SkipThisOrchestrationStep</Action>
                  </Precondition>
              </Preconditions>
              <ClaimsExchanges>
                  <ClaimsExchange Id="SignUpWithLogonEmailExchange" TechnicalProfileReferenceId="LocalAccountSignUpWithLogonEmail" />
              </ClaimsExchanges>
            </OrchestrationStep>

            <OrchestrationStep Order="3" Type="ClaimsExchange">
              <ClaimsExchanges>
                  <ClaimsExchange Id="GenerateOtpExchange" TechnicalProfileReferenceId="GenerateOtp" />
              </ClaimsExchanges>
            </OrchestrationStep>

            <OrchestrationStep Order="4" Type="ClaimsExchange">
              <ClaimsExchanges>
                  <ClaimsExchange Id="VerifyOtpExchange" TechnicalProfileReferenceId="VerifyOtp" />
              </ClaimsExchanges>
            </OrchestrationStep>

            <OrchestrationStep Order="5" Type="ClaimsExchange">
              <ClaimsExchanges>
                  <ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
              </ClaimsExchanges>
            </OrchestrationStep>

            <OrchestrationStep Order="6" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />

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

</TrustFrameworkPolicy>

Can someone tell me what i'm doing wrong here?


Solution

  • In "VerifyOtp", the sample has:

    <InputClaim ClaimTypeReferenceId="verificationCode" PartnerClaimType="otpToVerify" />
    

    but you have:

    <InputClaim ClaimTypeReferenceId="otp" PartnerClaimType="otpToVerify" />
    

    Also, why did you replace "email" with "signInName" in Generate and Verify?