I have a Blazor web app (.NET 8) with interactive render mode WebAssembly, and I use https://github.com/IUCrimson/AspNet.Security.CAS for authentication. It's mostly working great, and I can see my claims on the client and server. However, authorization only works for Razor pages. The authorize attribute on my controller is ignored. Is there some step I am missing in my setup?
Program.cs
using AspNetCore.Security.CAS;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization.Policy;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.FluentUI.AspNetCore.Components;
using System.Security.Claims;
var builder = WebApplication.CreateBuilder(args);
ConfigurationManager configuration = builder.Configuration;
IWebHostEnvironment environment = builder.Environment;
builder.Services.AddRazorComponents()
.AddInteractiveWebAssemblyComponents();
builder.Services.AddScoped<AuthenticationStateProvider, PersistingServerAuthenticationStateProvider>();
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options => {
options.AccessDeniedPath = "/AccessDenied";
options.Cookie.SameSite = SameSiteMode.Strict;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.ExpireTimeSpan = TimeSpan.FromHours(1); // set to match CAS timeout
options.LoginPath = new PathString("/api/login");
options.SlidingExpiration = false;
options.Events = new CookieAuthenticationEvents {
OnSigningIn = context => {
var _dal = new DatabaseService(configuration);
var principal = context.Principal;
ClaimsIdentity identity = (ClaimsIdentity)context.Principal.Identity;
var claims = new List<Claim> {
new(ClaimTypes.Role, "ADMIN")
};
identity.AddClaims(claims);
return Task.FromResult(0);
}
};
})
.AddCAS(options => {
options.CasServerUrlBase = "myCasServer";
options.ServiceForceHTTPS = true;
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
});
builder.Services.AddHttpClient();
builder.Services.AddSingleton<DatabaseService, DatabaseService>();
builder.Services.AddControllers();
builder.Services.AddFluentUIComponents();
var app = builder.Build();
if (app.Environment.IsDevelopment()) {
app.UseWebAssemblyDebugging();
} else {
app.UseExceptionHandler("/Error", createScopeForErrors: true);
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.UseStaticFiles();
app.UseAntiforgery();
app.MapRazorComponents<App>()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(OnlinePab.Client._Imports).Assembly);
app.MapControllers();
app.Run();
Controller
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Boo.Controllers {
[Authorize(Roles = "NOBODY")]
[Route("api")]
[ApiController]
public class MyController() : ControllerBase {
[AllowAnonymous]
[Route("login")]
public async Task Login(string returnUrl) {
var props = new AuthenticationProperties { RedirectUri = returnUrl };
await HttpContext.ChallengeAsync("CAS", props);
}
[Route("boo")]
public ContentResult NotAllowed() {
return base.Content("<div>Boo!</div>", "text/html");
}
}
}
Trial and error led me to add app.UseRouting()
in Program.cs, which has fixed the issue. I'd love to hear a technical reason as to why this works, given that routing was working fine without it, but authorization was not.
var app = builder.Build();
app.UseRouting(); // Fix authorization for controller
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();
}