Сбой входа в AD B2C после регистрации с приглашением по электронной почте

azure-ad-b2c #azure-ad-b2c-custom-policy

#azure-ad-b2c #azure-ad-b2c-пользовательская политика

Вопрос:

Я выполнил руководство по регистрации с приглашением по электронной почте, и оно работает нормально, за одним исключением. После завершения процесса регистрации и перенаправления пользователя приложению не удается выполнить аутентификацию пользователя и отображается сообщение об ошибке.

Просмотр OnMessageReceived события из OpenIdConnectEvents показывает, что значение MessageReceivedContext.ProtocolMessage.IdToken равно нулю.

Отладка события onMessageReceived

Однако пользователь перенаправляется в /MicrosoftIdentity/Account/Error с id_token, который кажется допустимым.

введите описание изображения здесь

В этом OnRemoteFailure событии я могу обнаружить следующую ошибку:

 {
    "ClassName": "System.Exception",
    "Message": "OpenIdConnectAuthenticationHandler: message.State is null or empty.",
    "Data": null,
    "InnerException": null,
    "HelpURL": null,
    "StackTraceString": null,
    "RemoteStackTraceString": null,
    "RemoteStackIndex": 0,
    "ExceptionMethod": null,
    "HResult": -2146233088,
    "Source": null,
    "WatsonBuckets": null
}
 

Кстати, новый пользователь правильно добавлен в AD B2C и может войти в систему после этого. Не удается пройти проверку подлинности только в процессе регистрации, инициированном ссылкой электронной почты.

Вот полная SignUpInvitation.xml пользовательская политика:

 <?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="mytenant.onmicrosoft.com"
  PolicyId="B2C_1A_signup_invitation"
  PublicPolicyUri="http://mytenant.onmicrosoft.com/B2C_1A_signup_invitation"
  DeploymentMode="Development"
  UserJourneyRecorderEndpoint="urn:journeyrecorder:applicationinsights">

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

  <BuildingBlocks>
    <ClaimsSchema>
      <!-- Sample: Read only email address to present to the user-->
      <ClaimType Id="ReadOnlyEmail">
        <DisplayName>Verified Email Address</DisplayName>
        <DataType>string</DataType>
        <UserInputType>Readonly</UserInputType>
      </ClaimType>
    
      <!--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-up without invitation" />
        </InputParameters>
        <OutputClaims>
          <OutputClaim ClaimTypeReferenceId="errorMessage" TransformationClaimType="createdClaim" />
        </OutputClaims>
      </ClaimsTransformation>

      <!--Sample: Copy the email to ReadOnlyEmail claim type-->
      <ClaimsTransformation Id="CopyEmailAddress" TransformationMethod="FormatStringClaim">
        <InputClaims>
          <InputClaim ClaimTypeReferenceId="email" TransformationClaimType="inputClaim" />
        </InputClaims>
        <InputParameters>
          <InputParameter Id="stringFormat" DataType="string" Value="{0}" />
        </InputParameters>
        <OutputClaims>
          <OutputClaim ClaimTypeReferenceId="ReadOnlyEmail" TransformationClaimType="outputClaim" />
        </OutputClaims>
      </ClaimsTransformation>
    </ClaimsTransformations>
  </BuildingBlocks>  

  <ClaimsProviders>
    <ClaimsProvider>
      <DisplayName>Local Account</DisplayName>
      <TechnicalProfiles>
        <!--Sample: Sign-up self-asserted technical profile-->
        <TechnicalProfile Id="LocalAccountSignUpWithReadOnlyEmail">
          <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">Create</Item>
            <!-- Sample: Remove sign-up email verification -->
            <Item Key="EnforceEmailVerification">False</Item>
          </Metadata>
          <InputClaimsTransformations>
            <!--Sample: Copy the email to ReadOnlyEamil claim type-->
            <InputClaimsTransformation ReferenceId="CopyEmailAddress" />
          </InputClaimsTransformations>
          <InputClaims>
            <!--Sample: Set input the ReadOnlyEmail claim type to prefilled the email address-->
            <InputClaim ClaimTypeReferenceId="ReadOnlyEmail" />
            <InputClaim ClaimTypeReferenceId="displayName" />
            <InputClaim ClaimTypeReferenceId="givenName" />
            <InputClaim ClaimTypeReferenceId="surName" />
          </InputClaims>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="objectId" />
            <!-- Sample: Display the ReadOnlyEmail claim type (instead of email claim type)-->
            <OutputClaim ClaimTypeReferenceId="ReadOnlyEmail" 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" />
          </OutputClaims>
          <ValidationTechnicalProfiles>
            <ValidationTechnicalProfile ReferenceId="AAD-UserWriteUsingLogonEmail" />
          </ValidationTechnicalProfiles>
          <!-- Sample: Disable session management for sign-up page -->
          <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
        </TechnicalProfile>      
      </TechnicalProfiles>
    </ClaimsProvider>

    <ClaimsProvider>
      <DisplayName>Self Asserted</DisplayName>
      <TechnicalProfiles>
        <!-- Demo: Show error message-->
        <TechnicalProfile Id="SelfAsserted-Unsolicited">
          <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>
          <InputClaimsTransformations>
            <InputClaimsTransformation ReferenceId="CreateUnsolicitedErrorMessage" />
          </InputClaimsTransformations>         
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="errorMessage"/>
          </InputClaims>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="errorMessage"/>
          </OutputClaims>
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>

    <!--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>
          
            <!--Sample action required: replace with your endpoint location -->
            <Item Key="METADATA">https://mytenant.b2clogin.com/mytenant.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1A_SIGNUP_INVITATION</Item>

            <!-- <Item Key="IdTokenAudience">your_optional_audience_override</Item> -->
            <Item Key="issuer">https://localhost:44316/</Item>
          </Metadata>
          <OutputClaims>
            <!--Sample: Read the email claim from the id_token_hint-->
            <OutputClaim ClaimTypeReferenceId="email" />  
          </OutputClaims>
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>
  </ClaimsProviders>

  <UserJourneys>
    <UserJourney Id="SignUpInvitation">
        <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: Self-asserted sign-up page -->
        <OrchestrationStep Order="3" Type="ClaimsExchange">
            <ClaimsExchanges>
                <ClaimsExchange Id="LocalAccountSignUpWithReadOnlyEmail" TechnicalProfileReferenceId="LocalAccountSignUpWithReadOnlyEmail"/>
            </ClaimsExchanges>
        </OrchestrationStep>

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

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

  <RelyingParty>
    <DefaultUserJourney ReferenceId="SignUpInvitation" />
    <UserJourneyBehaviors>
        <JourneyInsights TelemetryEngine="ApplicationInsights" InstrumentationKey="64729310-c74e-45ab-a59e-f35f7ada76ee" DeveloperMode="true" ClientEnabled="false" 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" PartnerClaimType="email" />
        <InputClaim ClaimTypeReferenceId="surname" PartnerClaimType="surname" />
        <InputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="givenName" />
        <InputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="displayName"/>
      </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>
 

Редактировать

Как уже упоминалось, ссылка на приглашение перенаправляется на мое веб-приложение, поэтому я добавил следующий маршрут для обработки id_token_hint .

 [HttpGet("/redeem/{scheme?}")]
public IActionResult Redeem([FromRoute] string scheme, [Bind(Prefix = "id_token_hint")] string idTokenHint, string policy)
{
    scheme ??= OpenIdConnectDefaults.AuthenticationScheme;
    string redirectUrl = Url.Content("~/");

    AuthenticationProperties properties = new AuthenticationProperties
    {
        RedirectUri = redirectUrl,
        Items =
        {
            ["id_token_hint"] = idTokenHint,
            ["policy"] = policy
        }
    };

    return Challenge(properties, scheme);
}
 

StartUp.cs Мне пришлось добавить следующий блок, чтобы он работал правильно.

 services.AddMicrosoftIdentityWebAppAuthentication(Configuration, Constants.AzureAdB2C);

services.Configure<MicrosoftIdentityOptions>(options =>
{
    options.Events ??= new OpenIdConnectEvents();

    options.Events.OnRedirectToIdentityProvider  = async ctx =>
    {
        // Append token hint when present (ie. email invitation)
        if (ctx.Properties.Items.ContainsKey("id_token_hint"))
            ctx.ProtocolMessage.IdTokenHint = ctx.Properties.Items["id_token_hint"];

        await Task.CompletedTask.ConfigureAwait(false);
    };
});
 

Ответ №1:

Использование этого в вашем производственном приложении

Библиотеки аутентификации создают a state , когда поток аутентификации начинается из вашего приложения. В этом примере создается исходная ссылка на политику Azure AD B2C, также называемая ссылкой «Выполнить сейчас». Этот тип ссылки не подходит для экземпляра вашего рабочего приложения и должен использоваться только для тестирования образца.

Для производственного сценария ссылка, содержащая the id_token_hint , должна указывать на ваше приложение, https://myapp.com/redeem?hint=<id_token_hint value> . Приложение должно иметь действительный маршрут для обработки параметра запроса, соответствующего id_token_hint . Затем приложение должно использовать библиотеку аутентификации для запуска аутентификации по идентификатору политики AAD B2C, для которого это id_token_hint должно использоваться. Библиотека будет содержать метод для добавления параметров запроса к запросу проверки подлинности. См. Документацию для библиотеки, используемой для реализации этого.

Затем библиотека аутентификации создаст окончательную ссылку для аутентификации с id_token_hint добавлением как части параметра запроса. Теперь это будет действительный запрос на проверку подлинности, и ваш пользователь будет перенаправлен на политику Azure AD B2C из вашего приложения. Ваше приложение сможет правильно обработать ответ от Azure AD B2C.

Для одностраничных приложений см. Документацию здесь . Для приложений .Net см. Документацию здесь .

Комментарии:

1. Спасибо за ваш ответ, он действительно помог мне разобраться в потоке. Я отредактировал свой вопрос, чтобы добавить больше информации о решении.