Blazor .net 8 Own Authentication
Asked Answered
C

4

6

In the Blazor (server side) I use AuthenticationStateProvider to set the authentication details in the session and restore it. Everything works perfecly with AuthorizeView but when I want to use Authorize attribute I am getting following error.

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

When I add builder.Services.AddAuthenticationCore(); I am getting the next error.

InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).

I do not want to use builder.Services.AddAuthentication as it forces to me use authentication handler (like AddCookie()).

I want to use AuthenticationStateProvider for authentication handling. Is there any ready solution for that ?

Callosity answered 21/12, 2023 at 15:27 Comment(1)
I have exactly this problem! Hope it get fixed soon. The workaround below does not work. The error goes away, but authorization is not happening and access is always granted. Did you find any solution?Tijerina
S
5

I also had came across this issue when moving my Blazor Server App to .NET 8. So after many hours scratching my head I found a solution that I hope helps someone else down the track.

The way around it was for me to create a custom AuthenticationHandler that essentially returns an authorised claim for every new request. It seems that the ASP Core part of Blazor requires an initial claims authentication method on the connection of each new HTTP request but after that, Blazor only uses AuthenticationStateProvider to check if you can access components/pages with the [Authorize] attribute. So if you just automatically authorise all initial connections then spin your own AuthenticationStateProvider you get the required custom authentication for your application.

So what I did was create my own auth handler class:

public class CustomAuthenticationHandler(IOptionsMonitor<CustomAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder,IHttpContextAccessor httpContextAccessor)
    : AuthenticationHandler<CustomAuthOptions>(options, logger, encoder) 
    {

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        return AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(new GenericIdentity("CustomIdentity")), "CustomAuthenticationScheme"));
    }
}

public class CustomAuthOptions : AuthenticationSchemeOptions
{
    // Add any custom options here if needed in the future
}

Then add it as the the default Authentication Scheme in program.cs

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = "CustomScheme";
    options.DefaultChallengeScheme    = "CustomScheme";
})
.AddScheme<CustomAuthOptions, CustomAuthenticationHandler>("CustomScheme", options => { });

This gets around the error and lets you use your custom authentication via AuthenticationStateProvider. And even though the code "authorises" all initial connections it does not allow access to any pages/components marked with the [Authorize] attribute as that only uses the AuthenticationStateProvider now.

Just for completelness, I add my custom AuthenticationStateProvider into program.cs as such:

builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationProvider>();

Which in my case checks if a token has been set for the current user's circuit to say they are logged in - which happens on the login page. So far all my testing is working as expected with users only being able to access my secure pages when logged in correctly.

Again, hopefully that helps someone else out.

Salian answered 24/2, 2024 at 6:35 Comment(3)
Thanks. this resolved the issue I am having when using a custom AuthenticationStateProviderPterous
What is CustomAuthenticationProvider?Elytron
@RichardBarraclough that is the AuthenticationStateProvider you create to actually do the authorising of pages within Blazor. For instance in my "CustomAuthenticationProvider" I access a singleton service that matches each Blazor CircuitID against a list of users who have logged in successfully, if it does not find a match it does not authenticate the user. This is the mechanism that any page decorated with the [Authorize] attribute actually uses to authorise access.Salian
M
3

I cannot comment on your question because of the "popularity contest" that is the reputation, great policies guys, keep them up!

Anywho, the only thing I found is this issue on the asp.net github: https://github.com/dotnet/aspnetcore/issues/52326

The gist of it is that updating your project to use .NET 8.0 Program.cs munges things up. I think this must be a bug in the framework, because the same program with the old NET 7.0 Program.cs styled startup works correctly, even when the Target Framework is then updated.

I tracked the issue to the Authorization Services being added automatically, but when I reverted the code to calling AddRouting and AddAuthentication (without AddAuthorization), the error was:

InvalidOperationException: Endpoint / (/) contains authorization metadata, but a middleware was not found that supports authorization. Configure your application startup by adding app.UseAuthorization() in the application startup code. If there are calls to app.UseRouting() and app.UseEndpoints(...), the call to app.UseAuthorization() must go between them.

Again, this did not happen on .NET 7.0, so I reckon until the framework is more mature, we'll have to wait or cope with custom authentication being demoted to a 2nd class citizen (and keep the Program.cs as is).

Majuscule answered 21/12, 2023 at 16:43 Comment(0)
H
1

I had the same problem while trying to refresh an Authourize attributed page (And I'm assuming that's the case for you aswell). A refresh will be authorized by buil-in auth within AspNetCore, and since the AuthenticationStateProvider is not integrated in AspNetCore, your own auth will not stay in the loop per default. Quite annoying. For now, I'm using this simple workaround to keep my custom auth in the game at all times.

https://github.com/dotnet/aspnetcore/issues/52317#issuecomment-1830673284

Helminth answered 1/1, 2024 at 13:26 Comment(3)
Thanks, I have the same problem. How do I register the class in the workaround? Any guidance?Tijerina
@Tijerina builder.Services.AddSingleton<IAuthorizationMiddlewareResultHandler, BlazorAuthorizationMiddlewareResultHandler>():Helminth
thanks! It works. The error goes away, but now my custom authentication fails and access is always allowed. So now I have basically not authentication. Any idea's how to fix it?Tijerina
C
0

Like others, my app with a custom AuthenticationStateProvider that worked fine in .NET 5 now gets this error in .NET 8. MS's documentation is poor; they want everyone to use their 'identity' process and Azure, so it's no surprise they didn't put much effort into documenting it for custom use.

This code from @Nathan with the custom AuthenticationHandler fixed the error, which led to my next problem: all of my pages with Authorize(Roles="role") would no longer authorize.

Through testing I eventually determined this AuthenticationHandler is called for every page, and if it doesn't return a Role in my Authorize(Roles) list on my razor pages, it fails. (And it fails silently, with no errors, no indication as to what's wrong.)

I modified the code from Nathan to the following, and added 'Generic' to my Role lists on my pages, and tested to verify security still works, and all is well. That is, users without a valid role that isn't 'Generic' still can't access pages, and the AuthenticationHandler gives the Generic role long enough for the next phase of Role validation to process.

At least that's what my testing implies.

public class CustomAuthenticationHandler
    (IOptionsMonitor<CustomAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder) : AuthenticationHandler<CustomAuthOptions>
    (options, logger, encoder) 
{
    
    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var claims = new List<Claim>();
        
        claims.Add(new Claim(ClaimTypes.Role, "Generic"));            

        var identity = new ClaimsIdentity(claims, Scheme.Name);
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, Scheme.Name);

        return AuthenticateResult.Success(ticket);            
    }
}

public class CustomAuthOptions : AuthenticationSchemeOptions
{
    // Add any custom options here if needed in the future
}

Note that I adapted this version of HandleAuthenticateAsync from here.

It would be nice if MS provided a way to disable this new 'feature'.

Hopefully this helps someone down the road!

Cheesecake answered 17/10, 2024 at 17:30 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.