dotnet-core WebApp и несколько токенов доступа к веб-API с использованием AzureB2C, MSAL - PullRequest
0 голосов
/ 03 ноября 2019

У меня настроена аутентификация / авторизация для 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);
    }
}
  1. Передача области при получении токена не помогла. Маркер всегда нулевой.
  2. У учетной записи всегда есть область действия для Api1, но не для Api2.
  3. Область 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();

1 Ответ

0 голосов
/ 07 ноября 2019

Да, мы можем иметь несколько областей для одного и того же API, а не несколько областей для разных Apis.

В в этом образце мы получаем токен с указанными областями.

// Retrieve the token with the specified scopes
                var scope = new string[] { api1_scope };

                IConfidentialClientApplication cca = MsalAppBuilder.BuildConfidentialClientApplication();
                var accounts = await cca.GetAccountsAsync();
                AuthenticationResult result = await cca.AcquireTokenSilent(scope, accounts.FirstOrDefault()).ExecuteAsync();
                var accessToken=result.AccessToken;

Вы можете получить accessToken с разными областями API.

// Retrieve the token with the specified scopes
                var scope = new string[] { api2_scope };

                IConfidentialClientApplication cca = MsalAppBuilder.BuildConfidentialClientApplication();
                var accounts = await cca.GetAccountsAsync();
                AuthenticationResult result = await cca.AcquireTokenSilent(scope, accounts.FirstOrDefault()).ExecuteAsync();
                var accessToken=result.AccessToken;
...