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