Blazor .net 8 performing authentication with sessionStorage

Esma 20 Reputation points
2024-04-19T13:18:56.1633333+00:00

Hi,

I used sessionStorage to ensure that the user remains logged in throughout the session. I had my own pre built database with usernames and passwords, so I did not use migrations and did not implement this with IdentityRevalidatingAuthenticationStateProvider. One issue I encounter with sessionStorage is that when I refresh the page, I use the Fluent UI library and I briefly see a "not authorized" text.

I am not sure if this is the correct usage, or if it is wrong, as I do not receive any error messages. On the homepage, I used amCharts for graphics, and when I refresh this page, I see the following message in the Visual Studio output: "Exception thrown:'Microsoft.JSInterop.JSDisconnectedException' in System.Private.CoreLib.dll". Still, the application runs.

Also, sometimes the application fails to open, and sometimes it does not. Could this intermittent failure be due to the use of localStorage or sessionStorage?

using BlazorApp4.Services;
using Blazored.SessionStorage;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
    private ISessionStorageService _sessionStorage;
    private IUserService _userService;
    public CustomAuthenticationStateProvider(ISessionStorageService sessionStorage, IUserService userService)
    {
        _sessionStorage = sessionStorage;
        _userService = userService;
    }
    //prerendering sırasında ya da kullanıcı bilgisi olmadığında anonim kimlik döndürür
    public override Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        var anonymousUser = new ClaimsPrincipal(new ClaimsIdentity());
        return Task.FromResult(new AuthenticationState(anonymousUser));
    }
    public async Task LoadUserStateAsync()
    {
        var username = await _sessionStorage.GetItemAsStringAsync("username");
        ClaimsIdentity identity;
        if (!string.IsNullOrEmpty(username))
        {
            identity = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, username) }, "CustomAuthType");
        }
        else
        {
            identity = new ClaimsIdentity();
        }
        var user = new ClaimsPrincipal(identity);
        var currentState = new AuthenticationState(user);
        //var snapshot = new AuthenticationState(new ClaimsPrincipal(identity));
        //await Task.Yield();
        NotifyAuthenticationStateChanged(Task.FromResult(currentState));
    }
    //public async Task LoadUserStateAsync()
    //{
    //    var username = await _sessionStorage.GetItemAsStringAsync("username");
    //    ClaimsIdentity identity = !string.IsNullOrEmpty(username)
    //        ? new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, username) }, "CustomAuthType")
    //        : new ClaimsIdentity();
    //    var user = new ClaimsPrincipal(identity);
    //    NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user)));
    //}
    public async Task<bool> MarkUserAsAuthenticated(string username, string password)
    {
        var user = await _userService.ValidateUserCredentials(username, password);
        if (user != null)
        {
            await _sessionStorage.SetItemAsStringAsync("username", username);
            var identity = new ClaimsIdentity(new[]
            {
                new Claim(ClaimTypes.Name, username),
            }, CookieAuthenticationDefaults.AuthenticationScheme);
            var userPrincipal = new ClaimsPrincipal(identity);
            NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(userPrincipal)));
            return true;
        }
        return false;
    }
    public async Task MarkUserAsLoggedOut()
    {
        await _sessionStorage.RemoveItemAsync("username");
        var identity = new ClaimsIdentity();
        var user = new ClaimsPrincipal(identity);
        NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user)));
    }
}


MainLayout:

@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.FluentUI.AspNetCore.Components
@inherits LayoutComponentBase

@using FluentOrientation = Microsoft.FluentUI.AspNetCore.Components.Orientation;
@inject AuthenticationStateProvider AuthenticationStateProvider

<FluentLayout >
    <FluentHeader>
        Blazor App
    </FluentHeader>

    <CultureSelector />
    <FluentStack Orientation="FluentOrientation.Horizontal" Width="100%">

        <div>
            <FluentNavMenu @bind-Expanded="@Expanded" Width="200" Collapsible="true" Title="App Navigation Menu">
                <AuthorizeView>
                    <Authorized>
                        <FluentNavLink Href="/" Icon="@(new Icons.Regular.Size24.Home())">Anasayfa</FluentNavLink>
                        <FluentNavLink Href="personals" Icon="@(new Icons.Regular.Size24.People())">Personel Listesi</FluentNavLink>
                        <FluentNavLink Href="Account/Logout">Çıkış Yap</FluentNavLink>
                    </Authorized>
                    <NotAuthorized>
                        <FluentNavLink Href="Account/Login">Giriş Yap</FluentNavLink>
                    </NotAuthorized>
                </AuthorizeView>
            </FluentNavMenu>
        </div>

        <FluentBodyContent>
            <div class="content">
                @Body
            </div>
        </FluentBodyContent>

        <FluentToastProvider @rendermode=@InteractiveServer />
        <FluentDialogProvider @rendermode=@InteractiveServer />
        <FluentTooltipProvider @rendermode=@InteractiveServer />
        <FluentMessageBarProvider @rendermode=@InteractiveServer />

    </FluentStack>

</FluentLayout>


@code
{
    bool Expanded = true;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await ((CustomAuthenticationStateProvider)AuthenticationStateProvider).LoadUserStateAsync();
            StateHasChanged();

        }
    }

}




.NET
.NET
Microsoft Technologies based on the .NET software framework.
3,474 questions
Blazor
Blazor
A free and open-source web framework that enables developers to create web apps using C# and HTML being developed by Microsoft.
1,431 questions
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Bruce (SqlWork.com) 58,126 Reputation points
    2024-04-19T14:45:12.7766667+00:00

    Accessing local storage requires JavaScript. You can not call JavaScript or access session storage during server pre-render. If you are storing the user credentials in local storage, then the user will not be available during pre-render. You might as well disable pre-render, or pre-render should be a splash screen.

    To detect prerender (.net 8) just add:

    [CascadingParameter] HttpContext HttpContext {get; set; }
    

    Only during pre-render will it not be null.

    0 comments No comments