Ed L 005
Ed L 005

Reputation: 197

Blazor local storage SetItemAsync has race condition across pages

[https://code-maze.com/how-to-secure-blazor-webassembly-with-identityserver4/]

[https://github.com/Blazored/LocalStorage]

I have a Blazor WASM utilising IdentityServer4 (above guide) which use local storage as well.

In my "RemoteAuthenticatorView", I have hooked both OnLogInSucceeded & OnLogOutSucceeded events. The respective events will perform SetItemAsync & ClearAsync with local storage where there are some extra user details. User details is for the purpose of doing some minor data retrieval if required.

There are 2 issues right now

  1. LocalStorage.SetItemAsync is not fast enough as when the sub pages is initialized, the read from local storage will yield empty result. All storage retrieve is done in OnInitializedAsync of the Razor pages.
  2. The OnLogOutSucceeded event does not trigger therefore LocalStorage.ClearAsync is not called. A subsequent login with diff credential will retrieve previously stored user detail
<RemoteAuthenticatorView Action="@Action" OnLogInSucceeded="SetClaimsPrincipalData" OnLogOutSucceeded="ClearClaimsPrincipalData">
</RemoteAuthenticatorView>

private async Task SetClaimsPrincipalData()
{
    try
    {
        var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
        var user = authState.User;

        if (user.Identity.IsAuthenticated)
        {
            var loggedInAccount = UserData.GetClaimsValue(user);
            var loggedInAccountStr = JsonHelper.SerializeString(loggedInAccount);

            await this.localStorage.ClearAsync();
            await this.localStorage.SetItemAsync(DisplayConfigs.SessionStorageKeyUserData, loggedInAccountStr);
        }
    }
    catch (Exception ex)
    {
    }
}

private async Task ClearClaimsPrincipalData()
{
    try
    {
        await this.localStorage.ClearAsync();
    }
    catch (Exception ex)
    {
    }
}

//=====//

protected override async Task OnInitializedAsync()
{
    try
    {
        var savedUserDataString = await this.localStorage.GetItemAsync<string>(KeyUserData);
        this.loggedInAccount = JsonHelper.DeserializeString<LoggedInAccount>(savedUserDataString);

        //// Base init
        await base.OnInitializedAsync();
    }
    catch (Exception ex)
    {
        logger.Error(ex, ex.Message);
    }
    finally
    {
        this.StateHasChanged();
    }
}

Upvotes: 1

Views: 793

Answers (1)

clamchoda
clamchoda

Reputation: 4941

Whenever I run into a race condition with local or session storage I end up making a service that holds/accesses the data. The service uses a bool flag isInitialized and instead of directly accessing the storage I wait for the flag to be true before reading the data.

public bool isInitialized = false;
public async Task SetClaimsPrincipalData()
{
    // Set LocalStorage
    // .
    // .
    // .
    isInitialized = true;

}

public async Task<string> GetClaimsPrincipalData()
{
    while (!isInitialized) { await Task.Delay(100); }
    return await this.localStorage.GetAsync<string>(DisplayConfigs.SessionStorageKeyUserData);
}

Now when you call GetClaimsPrincipalData it must wait for SetClaimsPrincipalData to be completed before returning.

Upvotes: 3

Related Questions