Duende BFF Yarn не передает токены при использовании веб-клиента gRpc из приложения Blazor WebAssembly

Я создаю веб-приложение с ASP.NET Core 6.
У меня есть:

  1. Frontend.Client — Blazor WebAssembly с пользовательским интерфейсом
  2. Frontend.Server — ASP.NET Core, на котором размещается Blazor WebAssembly.
  3. Web Api — удаленная служба REST
  4. Служба gRpc — удаленная служба gRpc.
  5. Identity Provider — проект Duende с использованием Duende.Bff.Yarp.

Мой Frontend.Client настроен на вызов собственного BFF (Frontend.Server), в то время как сервер перенаправляет вызовы на службы REST и gRpc с помощью Duende.Bff.YARP .
Вызовы службы REST работают как положено: клиент передает токен автоматически, как по документации.
Моя проблема связана с вызовами gRpc, которые, похоже, не используют правильный HttpClient с AntiForgeryToken и токеном доступа, как следует.
Я знаю, что где-то пропустил некоторые настройки, но я не могу найти ни одного примера того, как использовать Duende с gRpcWebClient.

Моя конфигурация Frontend.Client содержит:

      builder.Services.AddScoped<AuthenticationStateProvider, BffAuthenticationStateProvider>();

builder.Services.AddTransient<AntiforgeryHandler>();

builder.Services.AddHttpClient("backend", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
    .AddHttpMessageHandler<AntiforgeryHandler>();
builder.Services.AddTransient(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("backend"));

builder.Services.AddSingleton(services => {
    var backendUrl = new Uri(builder.HostEnvironment.BaseAddress);
    var channel = GrpcChannel.ForAddress(backendUrl, new GrpcChannelOptions {
        HttpHandler = new GrpcWebHandler(new HttpClientHandler()),
    });
    return new Commenter.CommenterClient(channel);
});

Моя конфигурация Frontend.Server содержит:

      builder.Services.AddBff();
var proxyBuilder = builder.Services.AddReverseProxy().AddTransforms<AccessTokenTransformProvider>();
// Initialize the reverse proxy from the "ReverseProxy" section of configuration
proxyBuilder.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
builder.Services.AddAuthentication(options => {
    options.DefaultScheme = "cookie";
    options.DefaultChallengeScheme = "oidc";
    options.DefaultSignOutScheme = "oidc";
})
.AddCookie("cookie", options => {
    options.Cookie.Name = "__Host-blazor";
    options.Cookie.SameSite = SameSiteMode.Strict;
})
.AddOpenIdConnect("oidc", options => {
    options.Authority = "https://localhost:5007";

    options.ClientId = "photosharing.bff";
    options.ClientSecret = "A9B27D26-E71C-4C53-89A8-3DAB53CE1854";
    options.ResponseType = "code";
    options.ResponseMode = "query";

    options.Scope.Clear();
    options.Scope.Add("openid");
    options.Scope.Add("profile");
    options.Scope.Add("photosrest");
    options.Scope.Add("commentsgrpc");
    options.Scope.Add("offline_access");

    options.MapInboundClaims = false;
    options.GetClaimsFromUserInfoEndpoint = true;
    options.SaveTokens = true;
});

//code omitted for brevity

app.UseAuthentication();
app.UseBff();
app.UseAuthorization();
app.MapBffManagementEndpoints();

app.MapReverseProxy().AsBffApiEndpoint();

Файл appsettings.json, используемый для чтения конфигурации, содержит:

      "ReverseProxy": {
    "Routes": {
      "photosrestroute": {
        "ClusterId": "photosrestcluster",
        "Match": {
          "Path": "/photos/{*any}"
        },
        "Metadata": {
          "Duende.Bff.Yarp.TokenType": "User"
        }
      },
      "commentsgrpcroute": {
        "ClusterId": "commentsgrpccluster",
        "Match": {
          "Path": "/comments.Commenter/{*any}"
        },
        "Metadata": {
          "Duende.Bff.Yarp.TokenType": "User"
        }
      }
    },
    "Clusters": {
      "photosrestcluster": {
        "Destinations": {
          "photosrestdestination": {
            "Address": "https://localhost:5003/"
          }
        }
      },
      "commentsgrpccluster": {
        "Destinations": {
          "commentsgrpdestination": {
            "Address": "https://localhost:5005/"
          }
        }
      }
    }
  }

Когда мой клиент вызывает gRpc, я получаю ответ 401 Unauthorized и Duende.Bff регистрирует, что проверка AntiForgery не прошла, на самом деле запрос не имеет заголовка с X-CSRF 1 (в то время как вызовы REST Api делают ). Это предполагает, что клиент gRpc не использует HTTP-клиент, который использует Дуэнде.
Как подключить мой клиент gRpc к Duende?

ПРИМЕЧАНИЕ: до введения бита аутентификации/авторизации я использовал YARP напрямую, и вызовы gRpc работали нормально. Когда я добавил Дуэнде, он сломался.

1 ответ

Проблема заключалась в AntiforgeryHandler, так как я не добавил его в цепочку HttpHandlers моего gRpcChannel. Что я сделал, чтобы решить это, было

  1. Добавьте конструктор в мой AntiforgeryHandler, чтобы принять внутренний обработчик и передать его базовому классу.
  2. Прикрепите мой AntiforgeryHandler к цепочке HttpHandler, управляющей построением клиента grpc.

AntiforgeryHandler становится:

      namespace PhotoSharingApplication.Frontend.Client.DuendeAuth;

public class AntiforgeryHandler : DelegatingHandler {
    public AntiforgeryHandler() { }
    public AntiforgeryHandler(HttpClientHandler innerHandler) : base(innerHandler) { }
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
    request.Headers.Add("X-CSRF", "1");
    return base.SendAsync(request, cancellationToken);
  }
}

Конструкция клиента grpc в моем проекте Frontend.Client выглядит следующим образом:

      builder.Services.AddSingleton(services => {
    var backendUrl = new Uri(builder.HostEnvironment.BaseAddress);
    var channel = GrpcChannel.ForAddress(backendUrl, new GrpcChannelOptions {
        HttpHandler = new GrpcWebHandler(new AntiforgeryHandler(new HttpClientHandler())),
    });
    return new Commenter.CommenterClient(channel);
});
Другие вопросы по тегам