Home > Net >  Custom authentication scheme invoked after authorization middleware
Custom authentication scheme invoked after authorization middleware

Time:09-19

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.

The custom authentication scheme is used to pull out a token (a randomly generated alphanumeric string) from the header, check it against a stored value in the database and validate the expiry date. This is required to support the old authentication method in the previous API as downloaded apps are still connecting this way.

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);
    }
}

CodePudding user response:

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.

  • Related