Blazor Logout на вкладках браузера

Все исходники /  Язык программирования C# /  OS Windows /  Web ASP.NET /  Blazor /  Blazor Logout на вкладках браузера

Blazor Server - идентификация пользователей

Приложения Blazor Server работают через подключение в реальном времени, созданное с помощью SignalR (Введение в ASP.NET Core SignalR). Именно библиотека реального времени SignalR придаёт веб-приложениям комфортную интерактивность, обновляя контент без перезагрузки самой веб страницы. Автоматическое создание JavaScript кода с помощью программирования на C# создаёт мостик для входа прикладных программистов в страну веб творчества. Blazor молод, но быстро набирает авторитет.

В приложениях Blazor Server возможно использование идентификации пользователей на основе проверенной платформы ASP.NET Core Identity и Entity Framework (EF). Сервис идентификации добавляется в проекты, если в качестве механизма проверки подлинности выбрать отдельные учетные записи пользователей . Аутентификация может выполняться на основе cookie или других специальных маркеров доступа. Создание подобного веб-приложения описано в статье Авторизация в приложении Blazor Server.

Blazor Server - специфика выхода из системы

Вместе с вышеописанным, развитая интерактивность Blazor приложений требует специфического подхода к обеспечению безопасности открытых аккаунтов. Работая в браузере с компонентами Blazor, можно заметить, что при открытии нескольких вкладок браузера, выход из системы осуществляется только в одной из них. Выйдя из системы на одной вкладке, можно продолжать работать с приложением в другой.

До тех пор, пока принудительно не перегрузить страницу приложения, компонент будет работать так, как будто выхода из системы не было. Происходит это по причине положительного свойства Blazor компонентов - они не перегружаются, а обновляется JavaScript кодом только их контент и поэтому автоматический выход из аккаунта не происходит. В таких случая необходимо внимательно следить за открытыми вкладками и при выходе из системы обязательно закрывать все вкладки или полностью браузер.

Logout Blazor - решение проблемы

Исходный код прикрепленного приложения Blazor Server предлагает один из возможных подходов решения проблемы одновременного выхода из системы на всех открытых вкладок браузера. Данный подход также позволяет производить принудительный выход из аккаунта на открытых страницах приложения Blazor в разных браузерах.

Logout Blazor принцип действия

Суть способа: при инициировании пользователем выхода из системы, возбуждается глобальное событие для всего приложения. В ответном методе всех открытых компонентов приложения веб-страницы принудительно перенаправляются на главную с одновременным выходом из системы. На главной веб-странице приложение определяет подлинность пользователя и, при отсутствии идентификации текущего пользователя, происходит перенаправление на страницу логинирования.

Ниже описываются составляющие элементы процесса принудительного вывода из аккаунта веб-страниц открытых в других вкладках или браузерах .

Single-класс для выхода из системы

Для того чтобы создать общую функциональность для всех открытых страниц веб-приложения Blazor можно использовать класс в режиме службы Singleton. Классы работающие в данном режиме всегда создаются в приложении Blazor в единственном экземпляре. Любой компонент и открытая веб-страница приложения, даже в разных браузерах, при запросе будут получать один и тот же экземпляр класса. На всё веб-приложение единственный экземпляр Single-класса.

В исходнике прикрепленного приложения роль одиночного класса исполняет TrackUserLogout. Экземпляр данного класса отслеживает событие выхода из системы текущей веб-страницы и передаёт его страницам в других вкладках.

Листинг Single-класса отслеживающего событие выхода из системы:
// Класс для отслеживания выхода текущего юзера из системы.
public class TrackUserLogout
{
    // Для отслеживания пользователя используется его зарегистрированное имя,
    // но можно использовать и другие маркеры.
    public delegate void UserLogoutEventHandler(string userName);

    public event UserLogoutEventHandler UserOut;

    // Вызов события выхода юзера из системы.
    // Возбуждается в Areas\Identity\Pages\Account\LogOut.cshtml
    // Выполняется в Shared\MainLayout.razor
    public virtual void OnUserOut(string name) => UserOut?.Invoke(name);
}

Страница Logout.cshtml

При нажатии на ссылку выхода из системы, пользователь POST-методом HTML-формы, перенаправляется на страницу LogOut.html. На странице выхода непосредственно происходит выписывание учётной записи пользователя методом SignInManager.SignOutAsync(). Этого действия достаточно только для logout на текущей вкладке браузера.

Одновременно генерируется событие класса отслеживания выхода из системы _trackUserLogout.OnUserOut(userName), где в качестве идентификатора передаётся имя текущего пользователя. Данное сообщение-событие предназначено для инициирования на других вкладках и браузерах выхода пользователя с данным именем из системы.

После выхода текущая вкладка браузера перенаправляется на начальную страницу, где запускается сервис идентификации пользователя.

@page
@using Microsoft.AspNetCore.Identity
@using BlazorCommonLogout.Global

@* Без этой строчки браузер сервер отправит в браузер 
ошибку 400  ( HTTP ERROR 400 - страница не найдена) *@
@attribute [IgnoreAntiforgeryToken] 
@inject SignInManager SignInManager

@* Внедрение службы отслеживания запуска пользователем
выхода из системы *@
@inject TrackUserLogout _trackUserLogout


@functions {
    public async Task OnPost()
    {
        if (SignInManager.IsSignedIn(User))
        {
            // Запоминаем имя пользователя
            string userName = User.Identity.Name;

            // Выход пользователя из системы.
            await SignInManager.SignOutAsync();

            // Генерирование события выхода пользователя
            // из системы.
            _trackUserLogout.OnUserOut(userName);
        }

        return Redirect("~/");
    }
}

Каркас страниц MainLayout.razor

Общим компонентом для всех страниц приложения служит каркас MainLayout.razor. В его C#-коде создан обработчик события выхода пользователя из системы. Обработчик будет срабатывать на тех вкладках браузера которые были открыты дополнительно.

Непосредственно в этом компоненте невозможно инициировать user logout, поскольку идентификация связана с установкой и удалением cookies. Любое действие с куки заключается в отправке команды в HTTP-заголовке, который должен отправляться впереди любого контента веб-страницы. Поэтому обработчик события выхода из аккаунта принудительно перегружает страницу приложения на адрес удаления куки.

Код C# общего каркаса веб-страниц приложения:
@code{

    protected override void OnInitialized()
    {
        // Подключение обработчика события logout
        _trackUserLogout.UserOut += OnUserOut;
    }

    async void OnUserOut(string name)
    {
        var state = await _authenticationStateProvider.GetAuthenticationStateAsync();

        // Выход из системы производится только того пользователя, 
        // который инициировал это действие.
        // Имя пользователя уникально в системе.
        if (state.User.Identity.Name == name)
        {
            _navigation.NavigateTo("/delete-cookie", true);
        }

        // Код работать не будет. 
        //if (_SignInManager.IsSignedIn(state.User))
        {
            // Метод выписывания пользователя удаляет куки. Чтобы удалить куки,
            // необходимо добавить в заголовок HTTP соответствующую команду. 
            // Отправка заголовка HTTP должно отправляться впервую 
            // очередь, до отправки контента страницы.
            // В этом месте строка не работает, поскольку страница уже загружена
            // и заголовки HTTP уже отправлены.
            // await _signInManager.SignOutAsync();

            //_navigation.NavigateTo("/Identity/Account/Login", true);
        }
    }

    // Очистка перед удалением компонента.
    // Иначе будет выбрасываться исключение, 
    // в частности в строке метода события: 
    // _navigation.NavigateTo("/delete-cookie", true)
    public void Dispose()
    {
        _trackUserLogout.UserOut -= OnUserOut;
    }

}

Страница удаления cookies

Компонент приложения DeleteCookie.razor предназначен для одновременного принудительного вывода из системы веб-страниц которые пользователь открыл на других вкладках браузера.

Компонент использует один из методов вызывающихся перед рендерингом страницы, в самом начале жизненного цикла - это методы SetParametersAsync(...) или OnInitializedAsync().

Данные методы вызываются дважды: при статической отрисовке веб-страницы и когда происходит соединение по каналу SignalR (т.е. когда начинает работать функциональность компонента). Записать или удалить куки можно только в первых вызовах этих методов. При попытке работы с куки во вторых вызовах выбросится исключение Error: System.InvalidOperationException: The response headers cannot be modified because the response has already started.

В листинге кода прикрепленном ниже видно, что после инициирования выхода из системы, компонент перенаправляется на главную страницу, исключая повторный вызов метода. В итоге куки подлинности пользователя успешно удаляются.

Листинг веб-страницы удаления cookies:
@page "/delete-cookie"

@using Microsoft.AspNetCore.Identity

@inject NavigationManager _navigation
@inject SignInManager _signInManager
@inject AuthenticationStateProvider _authenticationStateProvider

<h3>Exit from system</h3>

@code {

    // Метод вызывается два раза. Первый раз можно модифицировать заголовок HTTP.
    public override async Task SetParametersAsync(ParameterView parameters)
    //protected override async Task OnInitializedAsync()
    {
        var state = await _authenticationStateProvider.GetAuthenticationStateAsync();

        // Выход из системы пользователя в другом браузере.
        if (_signInManager.IsSignedIn(state.User))
        {
            await _signInManager.SignOutAsync();
        }

        // Перенаправление всех  страниц на главную страницу.
        _navigation.NavigateTo("/", true);
    }
}

Исходник веб-приложения Blazor Logout

Исходник веб-приложение Blazor Server создан в Microsoft Visual Studio 2019, платформа NET5, язык программирования C#.

Скачать исходник