ThomasRogers
ThomasRogers

Reputation: 106

How can I use Blazored.LocalStorage in AuthenticationStateProvider (.Net 6)?

I am trying to use the localStorageService in my CustomAuthStateProvider class so I can create a AuthenticationState based on a key in local storage (just to learn and to practice).

However, when I run my application, I get an error telling me that no suitable constructor can be found for CustomAuthStateProvider. The error makes sense but I don't understand how I can fix it and haven't found much online.

Here is the error:

enter image description here

Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]

Unhandled exception rendering component: A suitable constructor for type 'BlazorBattles.Client.CustomAuthStateProvider' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.

System.InvalidOperationException: A suitable constructor for type 'BlazorBattles.Client.CustomAuthStateProvider' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.

Here is my CustomAuthStateProvider implementing AuthenticationStateProvider:

public class CustomAuthStateProvider : AuthenticationStateProvider
{
    private readonly ILocalStorageService _localStorageService;
    CustomAuthStateProvider(ILocalStorageService localStorageService)
    {
        _localStorageService = localStorageService;
    }

    public override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        if (await _localStorageService.GetItemAsync<bool>("isAuthenticated"))
        {
            ClaimsIdentity claimsIdentity = new ClaimsIdentity(new[]
            {
                new Claim(ClaimTypes.Name, "Thomas")
            }, "Test Authentication");

            ClaimsPrincipal user = new ClaimsPrincipal(claimsIdentity);
            AuthenticationState state = new AuthenticationState(user);

            //Tell all the components that the Auth state has changed
            NotifyAuthenticationStateChanged(Task.FromResult(state));
            return (state);
        }

        //This will result in an unauthorised user because it does not have a claims identity
        return (new AuthenticationState(new ClaimsPrincipal()));
    }
}

Here is my Program.cs

using BlazorBattles.Client;
using BlazorBattles.Client.Services;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Blazored.Toast;
using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components.Authorization;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddBlazoredToast();
builder.Services.AddBlazoredLocalStorage();
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddScoped<IBananaService, BananaService>();
builder.Services.AddScoped<IUnitService, UnitService>();
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthStateProvider>();
builder.Services.AddOptions();
builder.Services.AddAuthorizationCore();

await builder.Build().RunAsync();

I am using V4.3.0 for Blazored.LocalStorage and V6 for Microsoft.AspNetCore.Components.Authorization

Thanks.

It works as expected when I remove the constructor and references to LocalStorage but when I try to inject LocalStorage to use it then I get the error. I'm not sure how to make use of the constrctor correctly in this specific case?

Update: The solution to my problem here is to add the public keyword for the constructor

Upvotes: 1

Views: 4184

Answers (3)

ThomasRogers
ThomasRogers

Reputation: 106

The issue with my code above is that I had missed out the public keyword in my constructor and now it works as expected. A huge thank you to everyone who commented on my post and provided potential solutions, I appreciate the time you took to help me out!

Original code:

CustomAuthStateProvider(ILocalStorageService localStorageService)
{
    _localStorageService = localStorageService;
}

Updated code:

public CustomAuthStateProvider(ILocalStorageService localStorageService)
{
    _localStorageService = localStorageService;
}

Upvotes: 0

MrC aka Shaun Curtis
MrC aka Shaun Curtis

Reputation: 30046

I think your main issue is your custom AuthenticationStateProvider inheritance.

Here is my "Pass Through" WASM provider that injects (but never uses) Local Storage. It just gets the user from the base code. Note it's inheritance.

    public class CustomAuthenticationStateProvider 
        : RemoteAuthenticationService<RemoteAuthenticationState, RemoteUserAccount, MsalProviderOptions>
    {
        private readonly ILocalStorageService _localStorageService;

        public CustomAuthenticationStateProvider(
            IJSRuntime jsRuntime, 
            IOptionsSnapshot<RemoteAuthenticationOptions<MsalProviderOptions>> options, 
            NavigationManager navigation, 
            AccountClaimsPrincipalFactory<RemoteUserAccount> accountClaimsPrincipalFactory,
            ILocalStorageService localStorageService
            ) 
            : base(jsRuntime, options, navigation, accountClaimsPrincipalFactory)
        {
            _localStorageService= localStorageService;
        }

        public async override Task<AuthenticationState> GetAuthenticationStateAsync()
        {
            var auth = await base.GetAuthenticationStateAsync();

            return new AuthenticationState(auth.User ?? new ClaimsPrincipal());
        }
    }

For reference here's my Program using AzureAD for authentication.

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddHttpClient("Blazr.AzureOIDC.WASM.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

// Supply HttpClient instances that include access tokens when making requests to the server project
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("Blazr.AzureOIDC.WASM.ServerAPI"));

builder.Services.AddMsalAuthentication(options =>
{
    builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
    options.ProviderOptions.DefaultAccessTokenScopes.Add("api://api.id.uri/access_as_user");
});
builder.Services.AddBlazoredLocalStorage();
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();

await builder.Build().RunAsync();

Upvotes: 2

Dimitris Maragkos
Dimitris Maragkos

Reputation: 11332

Try to register CustomAuthStateProvider service like this:

// Make the same instance accessible as both AuthenticationStateProvider and CustomAuthStateProvider
builder.Services.AddScoped<CustomAuthStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(provider => provider.GetRequiredService<CustomAuthStateProvider>());

Upvotes: 2

Related Questions