I have two .NET 8 projects, an ASP.NET Core 8 Web API and Blazor web app with interactive render mode set to server.
The Web API handles the authentication and provides a JWT token. The registration and login for getting a token works fine.
The issue arises when I try to use <AuthorizeView>
. I added it to the Home.razor
component as a basic test but I was then met with an error:
JavaScript interop calls cannot be issued at this time
I have not been able to progress beyond this issue.
Program.cs
:
using AuthDemo.Blazor.Server.UI.Components;using AuthDemo.Blazor.Server.UI.Infrastructure.Services.HttpClients;using Blazored.LocalStorage;using AuthDemo.Blazor.Server.UI.Infrastructure.Services.Authentication;using Microsoft.AspNetCore.Components.Authorization;using AuthDemo.Blazor.Server.UI.Infrastructure.Providers.Authentication;var builder = WebApplication.CreateBuilder(args);// Add services to the container.builder.Services.AddRazorComponents() .AddInteractiveServerComponents();builder.Services.AddCascadingAuthenticationState();builder.Services.AddBlazoredLocalStorage();builder.Services.AddHttpClient<IAuthDemoApiClient, AuthDemoApiClient>(cl => cl.BaseAddress = new Uri("https://localhost:7287"));builder.Services.AddScoped<IAuthenticationService, AuthenticationService>();builder.Services.AddScoped<CustomAuthenticationStateProvider>();builder.Services.AddScoped<AuthenticationStateProvider>(p => p.GetRequiredService<CustomAuthenticationStateProvider>());builder.Services.AddRouting(options =>{ options.LowercaseUrls = true;});var app = builder.Build();// Configure the HTTP request pipeline.if (!app.Environment.IsDevelopment()){ app.UseExceptionHandler("/Error", createScopeForErrors: true); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts();}app.UseHttpsRedirection();app.UseStaticFiles();app.UseStatusCodePagesWithReExecute("/StatusCode/{0}");app.UseAntiforgery();app.MapRazorComponents<App>() .AddInteractiveServerRenderMode();app.Run();
App.razor
:
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><base href="/" /><link rel="stylesheet" href="bootstrap/bootstrap.min.css" /><link rel="stylesheet" href="app.css" /><link rel="stylesheet" href="AuthDemo.Blazor.Server.UI.styles.css" /><link rel="icon" type="image/png" href="favicon.png" /><HeadOutlet @rendermode="InteractiveServer" /></head><body><Routes @rendermode="InteractiveServer" /><script src="_framework/blazor.web.js"></script></body></html>
AuthenticationService.cs
:
using AuthDemo.Blazor.Server.UI.Infrastructure.Services.HttpClients;using Microsoft.AspNetCore.Identity.Data;namespace AuthDemo.Blazor.Server.UI.Infrastructure.Services.Authentication{ public class AuthenticationService : IAuthenticationService { private readonly IAuthDemoApiClient _AuthDemoApiClient; public AuthenticationService(IAuthDemoApiClient AuthDemoApiClient) { _AuthDemoApiClient = AuthDemoApiClient; } public async Task<AuthenticationResponseDto?> LoginAsync(LoginDto loginDto) { var result = await _AuthDemoApiClient.LoginAsync(loginDto); return result; } }}
CustomAuthenticationStateProvider.cs
:
using Blazored.LocalStorage;using Microsoft.AspNetCore.Components.Authorization;using System.IdentityModel.Tokens.Jwt;using System.Security.Claims;namespace AuthDemo.Blazor.Server.UI.Infrastructure.Providers.Authentication{ public class CustomAuthenticationStateProvider : AuthenticationStateProvider { private readonly ILocalStorageService _localStorageService; private ClaimsPrincipal _anonymous = new ClaimsPrincipal(new ClaimsIdentity()); private readonly JwtSecurityTokenHandler _jwtSecurityTokenHandler; public CustomAuthenticationStateProvider(ILocalStorageService localStorageService) { _localStorageService = localStorageService; _jwtSecurityTokenHandler = new JwtSecurityTokenHandler(); } public override async Task<AuthenticationState> GetAuthenticationStateAsync() { var token = await _localStorageService.GetItemAsync<string>("accessToken"); if (string.IsNullOrEmpty(token)) { return new AuthenticationState(_anonymous); } var tokenContent = _jwtSecurityTokenHandler.ReadJwtToken(token); var claims = tokenContent.Claims; var user = new ClaimsPrincipal(new ClaimsIdentity(claims, "jwt")); return await Task.FromResult(new AuthenticationState(user)); } public void AuthenticateUser(string token) { var tokenContent = _jwtSecurityTokenHandler.ReadJwtToken(token); var claims = tokenContent.Claims; var user = new ClaimsPrincipal(new ClaimsIdentity(claims, "jwt")); var state = new AuthenticationState(user); NotifyAuthenticationStateChanged(Task.FromResult(state)); } }}
Login.razor
:
@page "/auth/login"@inject IAuthenticationService _authenticationService@inject AuthenticationStateProvider _authenticationStateProvider@inject IAuthDemoApiClient _httpAuthDemoApiClient;@inject ILocalStorageService _localStorageService;@inject NavigationManager _navigationManager;<h3>Login</h3>@if (!string.IsNullOrEmpty(exMessage)){<div class="alert alert-danger"><p>@exMessage</p></div>}<div class="card-body"><EditForm Model="LoginDto" OnValidSubmit="HandleLogin"><DataAnnotationsValidator /><ValidationSummary /><div class="form-group"><label for="EmailAddress">Email Address</label><InputText class="form-control" @bind-Value="LoginDto.EmailAddress" /><ValidationMessage For="@(() => LoginDto.EmailAddress)" /></div><br /><div class="form-group"><label for="Password">Password</label><InputText type="password" class="form-control" @bind-Value="LoginDto.Password" /><ValidationMessage For="@(() => LoginDto.Password)" /></div><button type="submit" class="btn btn-primary btn-block">Login</button></EditForm></div>@code { LoginDto LoginDto = new LoginDto(); string exMessage = string.Empty; private async Task HandleLogin() { try { var authResponse = await _authenticationService.LoginAsync(LoginDto); if (authResponse != null) { await _localStorageService.SetItemAsync("token", authResponse.Token); ((CustomAuthenticationStateProvider)_authenticationStateProvider).AuthenticateUser(authResponse.Token!); _navigationManager.NavigateTo("/"); } } catch (ApiException ex) { exMessage = ex.Response; } catch (Exception ex) { exMessage = ex.Message; } }}
The above works, I can login and I get back a token.
The issue occurs when I add the following:
Home.razor
:
@page "/"<PageTitle>Home</PageTitle><h1>Hello, world!</h1>Welcome to your new app.<AuthorizeView><Authorized><h1>logged in</h1></Authorized><NotAuthorized><h1>not logged in</h1></NotAuthorized></AuthorizeView>
When I run the project I get the following error:
I get the same issue if I add <AuthorizeView>
to Routes.razor
.
I am trying understand what the correct approach is here ...