最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c# - Blazor Server: forceLoad: true causes "IAuthenticationService" error on page navigation - Stack Overflow

programmeradmin4浏览0评论

I am working on an internal tool that displays data from a database. The application uses authentication, managed by a CustomAuthStateProvider, which allows users to log in and out. Certain actions are restricted to authenticated users, and this functionality has been working correctly.

However, I encountered an issue when trying to navigate to a specific page. The navigation is triggered using the following line:
NavigationManager.NavigateTo($"/myPage/dateFrom={System.Uri.EscapeUriString(fromStr)}/dateTo={System.Uri.EscapeUriString(toStr)}", forceLoad: true);
The important part here is forceLoad: true. The error only occurs when forceLoad: true is used. When this navigation occurs, the application crashes with the following error message:

InvalidOperationException: Unable to find the required 'IAuthenticationService' service. Please add all the required services by calling 'IServiceCollection.AddAuthentication' in the application startup code.

Additional Finding
If I manually enter some URL of the app into the browser's address bar and press Enter, the same error occurs.

Further attempts
I tried refactoring some parts of the authentication logic and then encountered a new error:

The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action configureOptions).

In response to this, I attempted to use the solution suggested in this StackOverflow answer, but it did not resolve the issue.

Next, I tried to follow the approach outlined in this answer, but I ran into additional errors during implementation.

CustomAuthStateProvider.cs

    using Microsoft.AspNetCore.Components.Authorization;
    using System.Security.Claims;
    using System.Threading.Tasks;

    public class CustomAuthStateProvider : AuthenticationStateProvider
    {
        public CustomAuthStateProvider()
        {
            this.CurrentUser = this.GetAnonymous();
        }

        private ClaimsPrincipal CurrentUser { get; set; }

        private ClaimsPrincipal GetUser(string userName, uint id, string role)
        {
            var identity = new ClaimsIdentity(new[]
            {
                new Claim(ClaimTypes. Sid, id.ToString(new System.Globalization.CultureInfo("de-DE"))),
                new Claim(ClaimTypes.Name, userName),
                new Claim(ClaimTypes.Role, role)
            },  "Authentication type");
            return new ClaimsPrincipal(identity);
        }

        private ClaimsPrincipal GetAnonymous()
        {
            var identity = new ClaimsIdentity(new[]
            {
                new Claim(ClaimTypes.Sid, "0"),
                new Claim(ClaimTypes.Name, "Anonymous"),
                new Claim(ClaimTypes.Role, "Anonymous")
            }, null);
            return new ClaimsPrincipal(identity);
        }

        public override Task<AuthenticationState> GetAuthenticationStateAsync()
        {
            var task = Task.FromResult(new AuthenticationState(this.CurrentUser));
            return task;
        }

        public Task<AuthenticationState> ChangeUser(string username, uint id, string role)
        {
            this.CurrentUser = this.GetUser(username, id, role);
            var task = this.GetAuthenticationStateAsync();
            this.NotifyAuthenticationStateChanged(task);
            return task;
        }

        public Task<AuthenticationState> Logout()
        {
            this.CurrentUser = this.GetAnonymous();
            var task = this.GetAuthenticationStateAsync();
            this.NotifyAuthenticationStateChanged(task);
            return task;
        }
    }

Program.cs

var builder = WebApplication.CreateBuilder(args);

// Add MudBlazor services
builder.Services.AddMudServices();

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

builder.Services.AddSingleton<InitializationStateService>(); // Logging

builder.Services.AddScoped<CustomAuthStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<CustomAuthStateProvider>());
builder.Services.AddAuthorizationCore();

builder.Services.AddServerSideBlazor().AddCircuitOptions(options =>
{
    options.DetailedErrors = true;
    options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(0);
    options.DisconnectedCircuitMaxRetained = 0;
});

builder.Services.AddHealthChecks();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
}

app.UseStaticFiles();
app.UseAntifery();

app.UseHealthChecks("/health");

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

app.Run();

I am working on an internal tool that displays data from a database. The application uses authentication, managed by a CustomAuthStateProvider, which allows users to log in and out. Certain actions are restricted to authenticated users, and this functionality has been working correctly.

However, I encountered an issue when trying to navigate to a specific page. The navigation is triggered using the following line:
NavigationManager.NavigateTo($"/myPage/dateFrom={System.Uri.EscapeUriString(fromStr)}/dateTo={System.Uri.EscapeUriString(toStr)}", forceLoad: true);
The important part here is forceLoad: true. The error only occurs when forceLoad: true is used. When this navigation occurs, the application crashes with the following error message:

InvalidOperationException: Unable to find the required 'IAuthenticationService' service. Please add all the required services by calling 'IServiceCollection.AddAuthentication' in the application startup code.

Additional Finding
If I manually enter some URL of the app into the browser's address bar and press Enter, the same error occurs.

Further attempts
I tried refactoring some parts of the authentication logic and then encountered a new error:

The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action configureOptions).

In response to this, I attempted to use the solution suggested in this StackOverflow answer, but it did not resolve the issue.

Next, I tried to follow the approach outlined in this answer, but I ran into additional errors during implementation.

CustomAuthStateProvider.cs

    using Microsoft.AspNetCore.Components.Authorization;
    using System.Security.Claims;
    using System.Threading.Tasks;

    public class CustomAuthStateProvider : AuthenticationStateProvider
    {
        public CustomAuthStateProvider()
        {
            this.CurrentUser = this.GetAnonymous();
        }

        private ClaimsPrincipal CurrentUser { get; set; }

        private ClaimsPrincipal GetUser(string userName, uint id, string role)
        {
            var identity = new ClaimsIdentity(new[]
            {
                new Claim(ClaimTypes. Sid, id.ToString(new System.Globalization.CultureInfo("de-DE"))),
                new Claim(ClaimTypes.Name, userName),
                new Claim(ClaimTypes.Role, role)
            },  "Authentication type");
            return new ClaimsPrincipal(identity);
        }

        private ClaimsPrincipal GetAnonymous()
        {
            var identity = new ClaimsIdentity(new[]
            {
                new Claim(ClaimTypes.Sid, "0"),
                new Claim(ClaimTypes.Name, "Anonymous"),
                new Claim(ClaimTypes.Role, "Anonymous")
            }, null);
            return new ClaimsPrincipal(identity);
        }

        public override Task<AuthenticationState> GetAuthenticationStateAsync()
        {
            var task = Task.FromResult(new AuthenticationState(this.CurrentUser));
            return task;
        }

        public Task<AuthenticationState> ChangeUser(string username, uint id, string role)
        {
            this.CurrentUser = this.GetUser(username, id, role);
            var task = this.GetAuthenticationStateAsync();
            this.NotifyAuthenticationStateChanged(task);
            return task;
        }

        public Task<AuthenticationState> Logout()
        {
            this.CurrentUser = this.GetAnonymous();
            var task = this.GetAuthenticationStateAsync();
            this.NotifyAuthenticationStateChanged(task);
            return task;
        }
    }

Program.cs

var builder = WebApplication.CreateBuilder(args);

// Add MudBlazor services
builder.Services.AddMudServices();

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

builder.Services.AddSingleton<InitializationStateService>(); // Logging

builder.Services.AddScoped<CustomAuthStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<CustomAuthStateProvider>());
builder.Services.AddAuthorizationCore();

builder.Services.AddServerSideBlazor().AddCircuitOptions(options =>
{
    options.DetailedErrors = true;
    options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(0);
    options.DisconnectedCircuitMaxRetained = 0;
});

builder.Services.AddHealthChecks();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
}

app.UseStaticFiles();
app.UseAntifery();

app.UseHealthChecks("/health");

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

app.Run();
Share Improve this question edited Mar 31 at 8:45 Qiang Fu 9,3671 gold badge6 silver badges16 bronze badges asked Mar 31 at 8:30 DanielDaniel 5522 silver badges16 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

You are using InteractiveServer rendermode which uses SSR(Http) 1st and websocket(No http) later.
When you reload the page, it will use SSR 1st, which treated @attribute [Authorize] like MVC/WebApi project, requiring you to add authentication scheme to check the identity. After that, it will proceed to AuthenticationStateProvider check.

A simple solution is using disable SSR rendermode
@rendermode @(new InteractiveServerRenderMode(false)).

Another solution is bypass the scheme check by implement IAuthorizationMiddlewareResultHandler

    public class BlazorAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
    {
        public Task HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
        {
            return next(context);
        }
    }

Then add it to program.cs

builder.Services.AddSingleton<IAuthorizationMiddlewareResultHandler, BlazorAuthorizationMiddlewareResultHandler>();

Just note that to for @attribute [Authorize] to work will need to use AuthorizeRouteView instead of RouteView in Routes.razor.

发布评论

评论列表(0)

  1. 暂无评论