ASP.NET Core 2.0 authentication middleware

So, after a long day of trying to solve this problem, I’ve finally figured out how Microsoft wants us to make custom authentication handlers for their new single-middleware setup in core 2.0.

After looking through some of the documentation on MSDN, I found a class called AuthenticationHandler<TOption> that implements the IAuthenticationHandler interface.

From there, I found an entire codebase with the existing authentication schemes located at https://github.com/aspnet/Security

Inside of one of these, it shows how Microsoft implements the JwtBearer authentication scheme. (https://github.com/aspnet/Security/tree/master/src/Microsoft.AspNetCore.Authentication.JwtBearer)

I copied most of that code over into a new folder, and cleared out all the things having to do with JwtBearer.

In the JwtBearerHandler class (which extends AuthenticationHandler<>), there’s an override for Task<AuthenticateResult> HandleAuthenticateAsync()

I added in our old middleware for setting up claims through a custom token server, and was still encountering some issues with permissions, just spitting out a 200 OK instead of a 401 Unauthorized when a token was invalid and no claims were set up.

I realized that I had overridden Task HandleChallengeAsync(AuthenticationProperties properties) which for whatever reason is used to set permissions via [Authorize(Roles="")] in a controller.

After removing this override, the code had worked, and had successfully thrown a 401 when the permissions didn’t match up.

The main takeaway from this is that now you can’t use a custom middleware, you have to implement it via AuthenticationHandler<> and you have to set the DefaultAuthenticateScheme and DefaultChallengeScheme when using services.AddAuthentication(...).

Here’s an example of what this should all look like:

In Startup.cs / ConfigureServices() add:

services.AddAuthentication(options =>
{
    // the scheme name has to match the value we're going to use in AuthenticationBuilder.AddScheme(...)
    options.DefaultAuthenticateScheme = "Custom Scheme";
    options.DefaultChallengeScheme = "Custom Scheme";
})
.AddCustomAuth(o => { });

In Startup.cs / Configure() add:

app.UseAuthentication();

Create a new file CustomAuthExtensions.cs

public static class CustomAuthExtensions
{
    public static AuthenticationBuilder AddCustomAuth(this AuthenticationBuilder builder, Action<CustomAuthOptions> configureOptions)
    {
        return builder.AddScheme<CustomAuthOptions, CustomAuthHandler>("Custom Scheme", "Custom Auth", configureOptions);
    }
}

Create a new file CustomAuthOptions.cs

public class CustomAuthOptions: AuthenticationSchemeOptions
{
    public CustomAuthOptions()
    {

    }
}

Create a new file CustomAuthHandler.cs

internal class CustomAuthHandler : AuthenticationHandler<CustomAuthOptions>
{
    public CustomAuthHandler(IOptionsMonitor<CustomAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
    {
        // store custom services here...
    }
    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        // build the claims and put them in "Context"; you need to import the Microsoft.AspNetCore.Authentication package
        return AuthenticateResult.NoResult();
    }
}

Leave a Comment