Вызов Api из blazor и передача токена аутентификации

#azure-active-directory #blazor-webassembly

#azure-active-directory #blazor-webassembly

Вопрос:

У меня есть проект blazor webassembly, который создается из https://github.com/Azure-Samples/ms-identity-blazor-wasm/tree/main/WebApp-graph-user/Call-MSGraph .

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

 dotnet new blazorwasm -au SingleOrg --client-id "{CLIENT ID}" -o {APP NAME} --tenant-id "{TENANT ID}"
 

Затем я смог вызвать graph.api, когда пользователь вошел в систему. Затем я попытался вызвать свой собственный Api с этой аутентификацией, как описано в https://docs.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/additional-scenarios?view=aspnetcore-3.1.

Я использовал

    builder.Services.AddHttpClient<ITestDataService, TestDataService>(
      client => client.BaseAddress = new Uri("https://localhost:44342/"))
        .AddHttpMessageHandler(x =>
        {
            var handler = x.GetRequiredService<AuthorizationMessageHandler>()
                .ConfigureHandler(new[] { "https://localhost:44342/" },
                    scopes: new[] { "https://graph.microsoft.com/User.Read" });

            return handler;
        });
 

Я вижу, что при вызове Api подключается токен, но проверка подлинности завершается с ошибкой (401). API создается из шаблонов Visual Studio для B2B AD и использует конфигурацию, которая также используется для приложения Blazor.

Это его Startup.cs

 public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
        .AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
    services.AddControllers();
}


        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();

        app.UseRouting();

        app.UseAuthentication();
        app.UseAuthorization();

        app.UseCors(policy =>
            policy.WithOrigins("http://localhost:5000", 
                "https://localhost:5001")
            .AllowAnyMethod()
            .WithHeaders(HeaderNames.ContentType,
                HeaderNames.Authorization,
                "x-custom-header")
            .AllowCredentials());

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
 

Вы хоть представляете, чего не хватает?

Полный исходный код доступен на github https://github.com/mathiasfritsch/blazor-calls-api

Ответ №1:

Если вы хотите вызвать Microsoft graph и свой пользовательский API в одном проекте blazor webassembly, мы можем реализовать это, создав другой HTTP-клиент для вызова другого API

Например

  • Регистрация приложения API сервера
    1. Зарегистрируйте приложение AAD для приложения API сервера
    2. Предоставить API
  • Регистрация клиентского приложения
    1. Регистрация клиентского приложения
    2. Включить неявный поток предоставления
    3. Добавьте разрешения API. (Разрешения Graph API и разрешения приложений API)
  • Настройка приложения API

Пожалуйста, добавьте следующий код в Startup.cs

  public void ConfigureServices(IServiceCollection services)
 {
        JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
        services.AddCors(options =>
            {
                options.AddDefaultPolicy(
                    builder => builder.AllowAnyOrigin()
                        .AllowAnyHeader()
                        .AllowAnyMethod());
            });
               services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
                .AddAzureADBearer(options => Configuration.Bind("AzureAd", options));

            services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, options =>
            {
                options.Authority  = "/v2.0";


                options.TokenValidationParameters = new TokenValidationParameters
                {
                    
                    ValidIssuers = new[] {
                      $"https://sts.windows.net/{Configuration["AzureAD:TenantId"]}/",
                      $"https://login.microsoftonline.com/{Configuration["AzureAD:TenantId"]}/v2.0"

                    },
                    RoleClaimType = "roles",
                    // The web API accepts as audiences both the Client ID (options.Audience) and api://{ClientID}.
                    ValidAudiences = new[]
                    {
                           options.Audience,
                           $"api://{options.Audience}"
                    }

                };

            });
....
}
  public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseSwagger();
            app.UseSwaggerUI(c =>
            {
                c.OAuthClientId(Configuration["Swagger:ClientId"]);
                c.OAuthScopeSeparator(" ");
                c.OAuthAppName("Protected Api");

                c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
            });
            app.UseHttpsRedirection();

            app.UseRouting();
            app.UseCors();
            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
 
  • Настройка клиентского приложения
  1. Создайте пользовательский обработчик сообщений AuthorizationMessageHandler для Graph API и пользовательского API
 // custom API
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
{
    public CustomAuthorizationMessageHandler(IAccessTokenProvider provider,
        NavigationManager navigationManager)
        : base(provider, navigationManager)
    {
        ConfigureHandler(
            authorizedUrls: new[] { "https://localhost:44300/" },
            scopes: new[] { "the API app scope" });
    }
}
 
 //Graph API
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class GraphAuthorizationMessageHandler : AuthorizationMessageHandler
{
    public GraphAuthorizationMessageHandler(IAccessTokenProvider provider,
        NavigationManager navigationManager)
        : base(provider, navigationManager)
    {
        ConfigureHandler(
            authorizedUrls: new[] { "https://graph.microsoft.com/" },
            scopes: new[] { "https://graph.microsoft.com/User.Read" });
    }
}
 
  1. Добавьте следующий код в program.cs
 public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            builder.RootComponents.Add<App>("app");

            builder.Services.AddScoped<CustomAuthorizationMessageHandler>();
            builder.Services.AddScoped<GraphAuthorizationMessageHandler>();
            // register HTTP client to call our own api
            builder.Services.AddHttpClient("MyAPI", client => client.BaseAddress = new Uri("https://localhost:44300/"))
              .AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
            // register HTTP client to call graph api
            builder.Services.AddHttpClient("GraphAPI", client => client.BaseAddress = new Uri("https://graph.microsoft.com/"))
              .AddHttpMessageHandler<GraphAuthorizationMessageHandler>();
  
            builder.Services.AddMsalAuthentication(options =>
            {
                builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
                options.ProviderOptions.DefaultAccessTokenScopes.Add("<the API app scope>");
                options.ProviderOptions.AdditionalScopesToConsent.Add("https://graph.microsoft.com/User.Read");
            });

            await builder.Build().RunAsync();
        }
    }
 
  1. Вызов api
 @inject IHttpClientFactory _clientFactory

var httpClient = _clientFactory.CreateClient("<the client name you register>");
await apiClient.GetStringAsync("path");
 

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