I wanted to set up authentication in Blazor WebAssembly, JWT, Net 8, but I encountered a problem. When I place tags in an interactive component, like here:
@page "/"@rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false))@inject IStringLocalizer<Login> Localizer@using Microsoft.AspNetCore.Components.Authorization<CascadingAuthenticationState><AuthorizeView><Authorized> Authorized</Authorized><NotAuthorized> NotAuthorized</NotAuthorized></AuthorizeView></CascadingAuthenticationState>My AuthenticationStateProvider works fine and returns Authorized or NotAuthorized. But when I try to do it in MainLayout.razor, like here:
@using Microsoft.AspNetCore.Components.Authorization@inherits LayoutComponentBase<CascadingAuthenticationState><AuthorizeView><Authorized> Authorized</Authorized><NotAuthorized> NotAuthorized</NotAuthorized></AuthorizeView></CascadingAuthenticationState>I always get NotAuthorized. This happens because the application is not rendered interactively in MainLayout.razor. How can I fix this?
My JwtAuthenticationStateProvider.cs:
using Blazored.LocalStorage;using Microsoft.AspNetCore.Components.Authorization;using Microsoft.JSInterop;using System.IdentityModel.Tokens.Jwt;using System.Security.Claims;namespace WMS.Client.Providers;public class JwtAuthenticationStateProvider(ILocalStorageService LocalStorage, IJSRuntime JSRuntime) : AuthenticationStateProvider{ private readonly ILocalStorageService _localStorage = LocalStorage; private readonly IJSRuntime _JSRuntime = JSRuntime; private const string TokenKey = "authToken"; public sealed override async Task<AuthenticationState> GetAuthenticationStateAsync() { if (_JSRuntime is not IJSInProcessRuntime) return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); var token = await _localStorage.GetItemAsync<string>(TokenKey); if (string.IsNullOrEmpty(token)) return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); var user = new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(token), "jwt")); return new AuthenticationState(user); } public async Task Login(string token) { await _localStorage.SetItemAsync(TokenKey, token); var user = new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(token), "jwt")); NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user))); } public async Task Logout() { await _localStorage.RemoveItemAsync(TokenKey); NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())))); } private static IEnumerable<Claim> ParseClaimsFromJwt(string token) { var handler = new JwtSecurityTokenHandler(); var jwt = handler.ReadJwtToken(token); return jwt.Claims; }}My Routes.razor:
@using Microsoft.AspNetCore.Components.Authorization<CascadingAuthenticationState><Router AppAssembly="@typeof(App).Assembly"><Found Context="routeData"><AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" /></Found><NotFound><LayoutView Layout="@typeof(Layout.MainLayout)"><p>Sorry, there's nothing at this address.</p></LayoutView></NotFound></Router></CascadingAuthenticationState>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 href="app.css" rel="stylesheet" /><link href="WMS.styles.css" rel="stylesheet" /><link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"><HeadOutlet /><script src="js/site.js"></script></head><body><Routes /><script src="_framework/blazor.web.js"></script></body></html>Program.cs in client side:
using Blazored.LocalStorage;using Microsoft.AspNetCore.Components.Authorization;using Microsoft.AspNetCore.Components.WebAssembly.Hosting;using System.Globalization;using WMS.Client.Providers;var builder = WebAssemblyHostBuilder.CreateDefault(args);builder.Services.AddBlazoredLocalStorage();builder.Services.AddLocalization(options => { options.ResourcesPath = "Resources"; });builder.Services.AddAuthorizationCore();builder.Services.AddScoped<JwtAuthenticationStateProvider>();builder.Services.AddScoped<AuthenticationStateProvider, JwtAuthenticationStateProvider>();CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US");CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("en-US");await builder.Build().RunAsync();Program.cs in server side:
using Blazored.LocalStorage;using Microsoft.AspNetCore.Components.Authorization;using WMS.Client.Providers;using WMS.Components;var builder = WebApplication.CreateBuilder(args);// Add services to the container.builder.Services.AddRazorComponents() .AddInteractiveWebAssemblyComponents();builder.Services.AddBlazoredLocalStorage();builder.Services.AddAuthorization();builder.Services.AddScoped<JwtAuthenticationStateProvider>();builder.Services.AddScoped<AuthenticationStateProvider, JwtAuthenticationStateProvider>();var app = builder.Build();// Configure the HTTP request pipeline.if (app.Environment.IsDevelopment()){ app.UseWebAssemblyDebugging();}else{ app.UseExceptionHandler("/Error", createScopeForErrors: true);}app.UseStaticFiles();app.UseAntiforgery();app.MapRazorComponents<App>() .AddInteractiveWebAssemblyRenderMode() .AddAdditionalAssemblies(typeof(WMS.Client._Imports).Assembly);app.Run();