esr124
esr124

Reputation: 51

How to setup a custom access denied page for Blazor server app

I have a .NET 8 Blazor Server app that is configured to use Microsoft Identity platform for authentication. The application has it's own User table in a SQL Server database that stores user information. There is a custom claims transformation service that is called every request to fetch the user information from the database and then add it to the ClaimsPrincipal. If the authenticated user does not have a user record a claim of "UserNotFound" is added with a value of "true". An authorization policy was added to check if there is not a "UserNotFound" claim with a value of "true", if there is they should get redirected to the access denied page. The policy seems to be working and is trying to redirect me to the MicrosoftIdentity/Account/AccessDenied page but it is not redirectly correctly. I'd like to have it redirect to a custom access denied page. I've tried changing it in the code and also in the appsetting.json with the other AzureAd settings but it's still trying to use the default Microsoft Identity URL. Does anyone know how to change this?

Here is my Program.cs:

using Demo.Auth.Blazor3.Components;
using Demo.Auth.Blazor3.Extensions;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(options =>
    {
        builder.Configuration.GetSection("AzureAd").Bind(options);
    });

builder.Services.AddControllersWithViews()
    .AddMicrosoftIdentityUI();

builder.Services.AddAuthorization(options =>
{
    // Custom policy to ensure user exists and is active
    options.AddPolicy("RequireValidUser", policy =>
    {
        policy.RequireAssertion(context =>
            !context.User.HasClaim(c =>
                (c.Type == "UserNotFound" && c.Value == "true") ||
                (c.Type == "UserArchived" && c.Value == "true")
            ));
    });

    // Set "RequireValidUser" as the FallbackPolicy, applying it globally
    options.FallbackPolicy = options.GetPolicy("RequireValidUser") ?? options.DefaultPolicy;
});

// Add custom services here.
builder.Services.AddCustomServices(builder.Configuration);


builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession();

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

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.UseAntiforgery();
app.UseSession();

// Add authentication and authorization middleware
app.UseAuthentication();
app.UseAuthorization();

// Map components and configure routes
app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();


app.Run();

Upvotes: 1

Views: 175

Answers (1)

esr124
esr124

Reputation: 51

In my Program.cs file I added:

builder.Services.Configure<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
    options.AccessDeniedPath = "/access-denied";
});

I added my custom AccessDenied.razor component/page and using

@attribute [Authorize(Roles = "Admin,Standard")]

at the top of pages or the AuthorizeView tag in the pages

<CascadingAuthenticationState>
                <AuthorizeView Policy="RequireValidUser" Context="ImpersonationComponent">
                    <Authorized>
                        <Login />
                    </Authorized>
                </AuthorizeView>
            </CascadingAuthenticationState>

I was able to get role based authorization working.

Upvotes: 0

Related Questions