У меня настроена аутентификация / авторизация для WebApp и Api, и она работает нормально. Проблема в том, когда мне нужно ввести дополнительные API, которые будут вызываться из WebAPP.
Ограничение состоит в том, что вы не можете запрашивать токен с областями, смешивающими веб-API-интерфейсы в одном вызове. Это ограничение службы (AAD), а не библиотеки. Вы должны попросить токен для https://{tenant}.onmicrosoft.com/api1/read, а затем вы можете приобрести токен для https://{tenant}.onmicrosoft.com/api2/read в режиме без вывода сообщений, поскольку это два разных APIS. Я узнал больше об этом из ТАК * и здесь
Поскольку нет полного примера, кроме нескольких строк кода, я пытаюсь найти лучший способреализации этого решения.
В настоящее время у меня настроена проверка подлинности при запуске
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
services.AddAzureAdB2C(options => Configuration.Bind("AzureAdB2C", options)).AddCookie();
AddAzureAdB2C - это настраиваемый метод расширения из Samples .
public static AuthenticationBuilder AddAzureAdB2C(this AuthenticationBuilder builder, Action<AzureAdB2COptions> configureOptions)
{
builder.Services.Configure(configureOptions);
builder.Services.AddSingleton<IConfigureOptions<OpenIdConnectOptions>, OpenIdConnectOptionsSetup>();
builder.AddOpenIdConnect();
return builder;
}
public class OpenIdConnectOptionsSetup : IConfigureNamedOptions<OpenIdConnectOptions>
{
public void Configure(OpenIdConnectOptions options)
{
options.ClientId = AzureAdB2COptions.ClientId;
options.Authority = AzureAdB2COptions.Authority;
options.UseTokenLifetime = true;
options.TokenValidationParameters = new TokenValidationParameters() { NameClaimType = "name" };
options.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProvider = OnRedirectToIdentityProvider,
OnRemoteFailure = OnRemoteFailure,
OnAuthorizationCodeReceived = OnAuthorizationCodeReceived
};
}
public Task OnRedirectToIdentityProvider(RedirectContext context)
{
var defaultPolicy = AzureAdB2COptions.DefaultPolicy;
if (context.Properties.Items.TryGetValue(AzureAdB2COptions.PolicyAuthenticationProperty, out var policy) &&
!policy.Equals(defaultPolicy))
{
context.ProtocolMessage.Scope = OpenIdConnectScope.OpenIdProfile;
context.ProtocolMessage.ResponseType = OpenIdConnectResponseType.IdToken;
context.ProtocolMessage.IssuerAddress = context.ProtocolMessage.IssuerAddress.ToLower().Replace(defaultPolicy.ToLower(), policy.ToLower());
context.Properties.Items.Remove(AzureAdB2COptions.PolicyAuthenticationProperty);
}
else if (!string.IsNullOrEmpty(AzureAdB2COptions.ApiUrl))
{
context.ProtocolMessage.Scope += $" offline_access {AzureAdB2COptions.ApiScopes}";
context.ProtocolMessage.ResponseType = OpenIdConnectResponseType.CodeIdToken;
}
return Task.FromResult(0);
}
}
Я предполагаю, что область должна быть установлена в этой строке для каждого API, но это является частью конвейера. (В противном случае, если метод OnRedirectToIdentityProvide выше)
context.ProtocolMessage.Scope += $" offline_access {AzureAdB2COptions.ApiScopes}";
Ниже приведены настройки клиента API
services.AddHttpClient<IApiClient1, ApiClient1>()
.AddHttpMessageHandler<API1AccessTokenHandler>();
services.AddHttpClient<IApiClient2, ApiClient2>()
.AddHttpMessageHandler<API2AccessTokenHandler>();
Ниже приведен код для автоматического получения токена для API1.
public class API1AccessTokenHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
IConfidentialClientApplication publicClientApplication = null;
try
{
// Retrieve the token with the specified scopes
scopes = AzureAdB2COptions.ApiScopes.Split(' ');
string signedInUserID = _httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
publicClientApplication = ConfidentialClientApplicationBuilder.Create(AzureAdB2COptions.ClientId)
.WithRedirectUri(AzureAdB2COptions.RedirectUri)
.WithClientSecret(AzureAdB2COptions.ClientSecret)
.WithB2CAuthority(AzureAdB2COptions.Authority)
.Build();
new MSALStaticCache(signedInUserID, _httpContextAccessor.HttpContext).EnablePersistence(publicClientApplication.UserTokenCache);
var accounts = await publicClientApplication.GetAccountsAsync();
result = await publicClientApplication.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
.ExecuteAsync();
}
catch (MsalUiRequiredException ex)
{
}
if (result.AccessToken== null)
{
throw new Exception();
}
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
return await base.SendAsync(request, cancellationToken);
}
}
Ниже приведен код для автоматического получения токена для API2, API2AccessTokenHandler.
public class API2AccessTokenHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
IConfidentialClientApplication publicClientApplication = null;
try
{
// Retrieve the token with the specified scopes
scopes = Constants.Api2Scopes.Split(' ');
string signedInUserID = _httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
publicClientApplication = ConfidentialClientApplicationBuilder.Create(AzureAdB2COptions.ClientId)
.WithRedirectUri(AzureAdB2COptions.RedirectUri)
.WithClientSecret(AzureAdB2COptions.ClientSecret)
.WithB2CAuthority(AzureAdB2COptions.Authority)
.Build();
new MSALStaticCache(signedInUserID, _httpContextAccessor.HttpContext).EnablePersistence(publicClientApplication.UserTokenCache);
var accounts = await publicClientApplication.GetAccountsAsync();
result = await publicClientApplication.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
.ExecuteAsync();
}
catch (MsalUiRequiredException ex)
{
}
if (result.AccessToken== null)
{
throw new Exception();
}
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
return await base.SendAsync(request, cancellationToken);
}
}
- Передача области при получении токена не помогла. Маркер всегда нулевой.
- У учетной записи всегда есть область действия для Api1, но не для Api2.
- Область APi1 добавляется из AzureB2COptions.ApiScope как часть кода конвейера ServiceCollection в Startup.cs
Я полагаю, что отдельные вызовы токена Acquire не помогают в случаеApi2, потому что область действия для Api1 устанавливается в Startup.cs.
Пожалуйста, предоставьте ваши ценные предложения вместе с примерами кода.
ОБНОВЛЕНИЕ: Я ищу что-то похожее на WithExtraScopeToConsent , которое разработано для IPublicClientApplication. AcquireTokenInteractive. Мне нужно подобное расширение для ConfidentialClientApplicationBuilder, чтобы использовать его для AcquireTokenByAuthorizationCode
cca.AcquireTokenByAuthorizationCode(AzureAdB2COptions.ApiScopes.Split(' '), code)
.WithExtraScopeToConsent(additionalScopeForAPi2)
.ExecuteAsync();