I am creating a Blazor UI application to talk to an API. Currently, I am attempting to get a custom provider for this working and I am getting a few errors.
This is the customer AuthenticationStateProvider
public class APIAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly HttpClient _httpClient;
private readonly ILocalStorageService _localStorage;
public APIAuthenticationStateProvider(HttpClient httpClient, ILocalStorageService localStorage)
{
_httpClient = httpClient;
_localStorage = localStorage;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var token = await _localStorage.GetItemAsync<string>("authToken");
if (string.IsNullOrWhiteSpace(token))
{
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
}
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("bearer", token);
var claims = ParseClaims(token);
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(claims,"jwt")));
}
}
My Configure Services function in Startup.cs file looks like this
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddBlazoredLocalStorage();
services.AddScoped<AuthenticationStateProvider, APIAuthenticationStateProvider>();
services.AddTransient<IAuthRepository, AuthRepository>();
services.AddHttpClient();
}
When I executed I get the following exception
InvalidOperationException: 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.
Microsoft.AspNetCore.Components.Server.Circuits.RemoteJSRuntime.BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
Microsoft.JSInterop.JSRuntime.InvokeAsync<TValue>(string identifier, CancellationToken cancellationToken, object[] args)
Microsoft.JSInterop.JSRuntime.InvokeWithDefaultCancellation<T>(string identifier, object[] args)
System.Threading.Tasks.ValueTask<TResult>.get_Result()
System.Runtime.CompilerServices.ValueTaskAwaiter<TResult>.GetResult()
Blazored.LocalStorage.LocalStorageService.GetItemAsync<T>(string key)
BookStore_UI.Providers.APIAuthenticationStateProvider.GetAuthenticationStateAsync() in APIAuthenticationStateProvider.cs
I am not sure if the error is being clear and that is where the error really is, or if there is some other underlying step that I am missing. I think it might have to do with the use of Local Storage.
in the _Host.cshtml, change the render-mode
render-mode="ServerPrerendered"
to render-mode="Server"
This code: var token = await _localStorage.GetItemAsync<string>("authToken"); is executed while JavaScript is not available yet. Look up the docs how to deal with such cases when you try to access the local storage. They specifically show how to do such a task.
Your ConfigureServices should look something like this:
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddBlazoredLocalStorage();
services.AddHttpClient();
services.AddScoped<APIAuthenticationStateProvider>();
services.AddScoped<AuthenticationStateProvider>(provider =>
provider.GetRequiredService<APIAuthenticationStateProvider>());
services.AddTransient<IAuthRepository, AuthRepository>();
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
The GetAuthenticationStateAsync method should have a single objective: Returning the Authentication State object, which is why you shouldn't use it to configure the HttpClient object. You should do it (reading the Jwt token from the local storage and assigning it to the request' Authorization header) from the very location you are going to call a method of the HttpClient service.
Hope this helps...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With