[SOLVED] Custom authentication scheme invoked after authorization middleware

Issue

This Content is from Stack Overflow. Question asked by Jackson

We are rebuilding a Web API with .Net 6.

When adding in the authentication we need two authentication schemes, JWT and a custom token scheme.

services
    .AddAuthentication()
    .AddJwtBearer()
    .AddScheme<CustomAuthenticationOptions, CustomAuthentication>(
        "CustomAuthentication",
        "Custom Authentication",
        options => {}
    );

When registering the middleware we want to add some custom roles from our database, so have a custom middleware after authentication but before the roles have been authorized.

app.UseAuthentication();
app.UseMiddleware<AuthMiddleware>();
app.UseAuthorization();

This works great for the JWT Bearer authentication but doesn’t for the custom authentication scheme.

The AuthMiddleware gets invoked before our CustomAuthentication does, meaning we are not authenticated before we add custom roles.

If we move the app.UseMiddleware<AuthMiddleware>(); after app.UseAuthorization(); then the CustomAuthentication is called before AuthMiddleware but the roles have already been authorized and the API returns unauthorized as expected.

Question

Why does AddJwtBearer() call authentication and middleware at the correct time, but my custom scheme does not? Or is there a better way to do custom authentication?

Simplified files for reference

CustomAuthentication.cs

public class CustomAuthentication : AuthenticationHandler<CustomAuthenticationOptions>
{
    public CustomAuthentication(
        IOptionsMonitor<CustomAuthenticationOptions> options,
        ILoggerFactory logger,
        UrlEncoder encoder,
        ISystemClock clock
    ) : base(options, logger, encoder, clock) { }

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        // This is called too late
    }
}

AuthMiddleware.cs

public class AuthMiddleware
{
    private readonly RequestDelegate _next;

    public AuthMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext httpContext)
    {
        if ((httpContext.User?.Identity?.IsAuthenticated).GetValueOrDefault())
        {
            // This is not authenticated for custom authentication
        }

        await _next(httpContext);
    }
}



Solution

I would refactor your custom roles function from AuthMiddleware into a new service. Removing your AuthMiddleware entirely.

Then hook the jwt bearer OnTokenValidated event to add role claims to the authenticated principal;

services.AddAuthentication()
.AddJwtBearer(options => {
    if (options.Events == null)
        options.Events = new();
    var validate = options.Events.OnTokenValidated;
    options.Events.OnTokenValidated = async context => {
        var service = context.HttpContext.RequestServices.GetService<...>();
        await service.AddDbRoles(context.Principal);
    };
})

With a similar implementation within your CustomAuthentication handler.


This Question was asked in StackOverflow by Jackson and Answered by Jeremy Lakeman It is licensed under the terms of CC BY-SA 2.5. - CC BY-SA 3.0. - CC BY-SA 4.0.

people found this article helpful. What about you?