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