I have a .net 8 Blazor Wasm app which I am trying to add basic custom JWT authorisation to. (Users in our own db)
However when I go to the protected page it just shows screen with a 401 error. If I remove the authorize attribute the page works fine (although obviously not protected).
Here is the page
@page "/test"@using Microsoft.AspNetCore.Authorization@using Microsoft.AspNetCore.Components.Authorization@inject HttpClient Http@attribute [Authorize]<h3>Test</h3>@code {}and the Routes.razor
@using Microsoft.AspNetCore.Components.Authorization<Router AppAssembly="typeof(Program).Assembly"><Found Context="routeData"><AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)"><NotAuthorized><h1>Not authorized</h1></NotAuthorized></AuthorizeRouteView><FocusOnNavigate RouteData="routeData" Selector="h1" /></Found></Router>and the Custom Authentication Provider
using Blazored.LocalStorage;using Microsoft.AspNetCore.Components;using Microsoft.AspNetCore.Components.Authorization;using System.Net.Http.Headers;using System.Security.Claims;using System.Text.Json;namespace Web.Client.Auth{ public class CustomAuthStateProvider : AuthenticationStateProvider { private readonly HttpClient _httpClient; private readonly ILocalStorageService _localStorage; private readonly NavigationManager _navigationManager; private readonly AuthenticationState _anonymous; private readonly JsonSerializerOptions _options; public CustomAuthStateProvider(HttpClient httpClient, ILocalStorageService localStorage, NavigationManager navigationManager) { _httpClient = httpClient; _localStorage = localStorage; _navigationManager = navigationManager; _anonymous = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); _options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; } public override async Task<AuthenticationState> GetAuthenticationStateAsync() { var token = await _localStorage.GetItemAsync<string>("authToken"); if (string.IsNullOrWhiteSpace(token)) { return _anonymous; } var claims = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>(), "jwtAuthType")); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token); //CheckTimeSinceLogin(claims); return new AuthenticationState(claims); } public void NotifyUserAuthentication(string email) { var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, email) }, "jwtAuthType")); var authState = Task.FromResult(new AuthenticationState(authenticatedUser)); NotifyAuthenticationStateChanged(authState); } public void NotifyUserLogout() { var authState = Task.FromResult(_anonymous); NotifyAuthenticationStateChanged(authState); } }}Here is the Program.cs for the client app
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;using Microsoft.AspNetCore.Components.Authorization;using RecoWeb.Client.Auth;using Blazored.LocalStorage;var builder = WebAssemblyHostBuilder.CreateDefault(args);builder.Services.AddAuthorizationCore();builder.Services.AddCascadingAuthenticationState();builder.Services.AddHttpClient("VLA.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });builder.Services.AddBlazoredLocalStorage();builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthStateProvider>();await builder.Build().RunAsync();and for the server app
using Microsoft.AspNetCore.Authentication.JwtBearer;using Microsoft.AspNetCore.Components.Authorization;using Microsoft.EntityFrameworkCore;using Microsoft.IdentityModel.Tokens;using MyApp.DataService;using MyApp.DataService.Models;using MyApp.Interfaces;using MyApp.Services;using MyApp.Shared.Dtos;using MyAppWeb.Client.Auth;using MyAppWeb.Client.Pages;using MyAppWeb.Components;using MyAppWeb.Controllers;using StockDataImporter;using System.Text;var builder = WebApplication.CreateBuilder(args);var config = builder.Configuration;builder.Configuration.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);builder.Configuration.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true);builder.Configuration.AddJsonFile($"appsettings.local.json", optional: true);// Add services to the container.builder.Services.AddRazorComponents() .AddInteractiveWebAssemblyComponents();builder.Services.AddControllers(); // Add this linebuilder.Services.AddRazorPages();builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>{ var jwtSettings = config.GetSection("JWTSettings"); options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = jwtSettings["validIssuer"], ValidAudience = jwtSettings["validAudience"], ClockSkew = new TimeSpan(0, 1, 0), IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings["securityKey"])) };});builder.Services.AddDbContext<RecodbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")), ServiceLifetime.Transient);builder.Services.AddScoped<IDataService, DataService>();builder.Services.AddScoped<IStockDataImportService, StockDataImportService>();builder.Services.AddScoped<IPartService, PartService>();builder.Services.AddScoped<IStockService, StockService>();var app = builder.Build();// Configure the HTTP request pipeline.if (app.Environment.IsDevelopment()){ app.UseWebAssemblyDebugging();}else{ 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.UseAuthentication();app.UseHttpsRedirection();app.UseStaticFiles();app.UseRouting(); // Add this lineapp.UseAntiforgery();app.UseAuthorization(); // Add this lineapp.MapControllers();app.MapRazorComponents<App>() .AddInteractiveWebAssemblyRenderMode() .AddAdditionalAssemblies(typeof(RecoWeb.Client._Imports).Assembly);app.Run();What am I doing wrong?
Just to be clear, I am expecting the page to show the NotAuthorised content from the routes.razor.
It was never this hard in .net 7...