Blazor – LocalStorageService не работает в DelegatingHandler

Я использую библиотеку Blazored.LocalStorage для хранения токенов JWT, возвращаемых методом входа в API. Затем я снова использую его при каждом другом вызове API, чтобы получить его и отправить в качестве токена на предъявителя. Я перевожу свои ранее функциональные жестко закодированные HTTP-клиенты на использование библиотеки Refit, а также DelegatingHandler для добавления токена JWT в заголовок авторизации каждого запроса. Обработчик выглядит следующим образом:

internal sealed class AuthenticationHandler(ILocalStorageService storageService) : DelegatingHandler
{
    private readonly ILocalStorageService _storageService = storageService;

    protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var authToken = await _storageService.GetItemAsStringAsync("test", cancellationToken);

        if (!string.IsNullOrEmpty(authToken))
            request.Headers.Authorization = new AuthenticationHeaderValue("bearer", authToken);

        return await base.SendAsync(request, cancellationToken);
    }
}

Вот как я регистрирую своих клиентов Refit:

builder.Services.AddRefitClient<ISomeRefitClientInterface>(refitSettings)
    .ConfigureHttpClient(options =>
    {
        options.BaseAddress = new Uri(config["APIs:MainAPI"]!);
        options.Timeout = TimeSpan.FromMinutes(5);
    })
    .AddHttpMessageHandler<AuthenticationHandler>()
    .AddHttpMessageHandler<LoggingHandler>()
    .AddHttpMessageHandler<RetryHandler>();

Два последних обработчика работают отлично, но AuthenticationHandler не хочет иметь ничего общего с локальным хранилищем, поскольку выдает следующее исключение: JavaScript interop calls cannot be issued at this time. This is because the component is being statically rendererd. When prerendering is enabled, JavaScript interop calls can only be performed during the OnAfterRenderAsync lifecycle method.

Предварительный рендеринг отключен глобально во всем моем приложении, а локальное хранилище прекрасно работает в методе OnInitializedAsync любого компонента (чего не было бы, если бы предварительный рендеринг был включен). До использования Refit я жестко запрограммировал свои HTTP-клиенты, и аутентификация выполнялась таким же образом, но без использования DelegatingHandler. Каждый клиент унаследован от общего базового класса, и логика добавления токена в заголовок аутентификации была там и вызывалась вручную для каждого запроса.

Быстрый поиск в Google обнаруживает аналогичные проблемы, о которых сообщалось на GitHub, но я не вижу решения. Кажется, что библиотеки, использующие взаимодействие Javascript, просто не работают изнутри DelegatingHandlers, поскольку я пробовал Blazored.SessionStorage с идентичными результатами.

Есть ли способ получить доступ к локальному хранилищу из DelegatingHandler?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
155
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

По моему опыту, даже отключив предварительный рендерер (SSR) во всем мире, некоторые места все еще каким-то образом используют SSR. Единственный способ получить доступ к значению во всех режимах рендеринга — это cookie.
Вы можете использовать httpcontext.request/response.cookie в режиме SSR. И используйте JSinterop для управления файлами cookie браузера в режиме сервера Interacrive/WASM.
Сначала установите Microsoft.AspNetCore.Http 2.2.2 в клиентский проект.
Затем добавьте следующий скрипт внизу «App.razor».

<script>
    function getCookie(key) {
        const value = `; ${document.cookie}`;
        const parts = value.split(`; ${key}=`);
        if (parts.length === 2) return parts.pop().split(';').shift();
    }

    function setCookie(key, value) {
        document.cookie = `${key}=${value}`;
    }
</script>

Затем вы можете определить режим рендеринга по httpcontext для работы с файлом cookie.

@using Microsoft.AspNetCore.Http
...
        if (httpContext != null)   //when SSR httpContext is not null.
        {
            //get cookie
            var cookieValue = httpContext.Request.Cookies["key1"];
            //set cookie
            httpContext.Response.Cookies.Append("key1","value1");
        }
        else   // when InterActive Server/WASM , httpContext is null. So use javascript interop.
        {
            //get cookkie
            var cookieValue =await _JSruntime.InvokeAsync<string>("getCookie","key1");
            //set cookie
            await _JSruntime.InvokeVoidAsync("setCookie", "key1", "value1");
        }

Ответ принят как подходящий

Я НАКОНЕЦ-ТО понял это! Оказывается, вы можете получить вошедшего в систему пользователя (и токен аутентификации, если вы сопоставили его с каким-либо утверждением) непосредственно из AuthenticationStateProvider Blazor из DelegatingHandler. Чтобы заставить DI правильно работать с IHttpClientFactory, требуется несколько наворотов, но все это подробно описано здесь: https://learn.microsoft.com/en-us/aspnet/core/blazor/security/server/additional- сценарии?view=aspnetcore-8.0&preserve-view=true#access-authenticationstateprovider-in-outgoing-request-middleware

Я использовал аналогичный подход для аутентификации запросов API из моего приложения Blazor Wasm, хотя я не использую Refit. Я не писал свой собственный DelegatingHandler, а использовал AuthorizationMessageHandler, предоставленный Microsoft.

Мой код выглядит следующим образом:

var backendUrl = new Uri(builder.Configuration["BackendUrl"]);

builder.Services.AddScoped<AuthorizationMessageHandler>();
builder.Services
    .AddHttpClient<ApiClient>(options =>
    {
        options.BaseAddress = backendUrl;
        options.DefaultRequestVersion = HttpVersion.Version20;
        options.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher;
    })
    .AddHttpMessageHandler(sp => sp.GetRequiredService<AuthorizationMessageHandler>()
        .ConfigureHandler(authorizedUrls: new[] { backendUrl.AbsoluteUri }));

Затем вам также необходимо зарегистрировать IAccessTokenProvider следующим образом.

builder.Services.AddSingleton<IAccessTokenProvider, AccessTokenAuthenticationStateProvider>();

И в свой собственный AccessTokenAuthenticationStateProvider я добавляю Blazored ILocalStorageService, который здесь работает нормально.

К сожалению, этого не существует на сервере Blazor, который я использую.

Martin 09.07.2024 16:42

Другие вопросы по теме