azure-ad-b2c #azure-ad-b2c-custom-policy
#azure-ad-b2c #azure-ad-b2c-пользовательская политика
Вопрос:
Я выполнил руководство по регистрации с приглашением по электронной почте, и оно работает нормально, за одним исключением. После завершения процесса регистрации и перенаправления пользователя приложению не удается выполнить аутентификацию пользователя и отображается сообщение об ошибке.
Просмотр OnMessageReceived
события из OpenIdConnectEvents
показывает, что значение MessageReceivedContext.ProtocolMessage.IdToken
равно нулю.
Однако пользователь перенаправляется в /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. Спасибо за ваш ответ, он действительно помог мне разобраться в потоке. Я отредактировал свой вопрос, чтобы добавить больше информации о решении.