ASP.NET Core 2.0 Authentication Middleware

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

How to request authentication from custom middleware in ASP.NET Core 2.0

In custom middleware method Invoke() call ChallengeAsync() if user is not authenticated:

public async Task Invoke(HttpContext httpContext, IServiceProvider serviceProvider)
{
if (!httpContext.User.Identity.IsAuthenticated)
{
await httpContext.ChallengeAsync();
}
else { /* logic here */ }
}

NuGet package Microsoft.AspNetCore.Authentication.Abstractions has to be added.

Above code will run the default authentication service to authenticate user. If the default one is your custom authentication middleware, then it will be called.

Proper way to extend the authentication middleware in .NET Core 2.0

This seems more of a style/design issue.

A) You can indeed put everything into an AccountController, during the 'login' you can just add the 'registration' to be part of it (in case you find an 'external' match for the user credentials), pretty much as you suggested.

I don't see a huge downside to doing just that (though I often prefer middleware myself as you can more 'configure' things in startup).

Design wise, there is probably an issue synchronizing your external provider with the internal identity store (if that's what's the case, usually is), but that goes for either of these options. Often introducing a specialized authorization server may solve that but I don't know enough specifics so just guessing.

B) if I'm reading you write what you want,

to add the auth. middleware I used something like this (based on it):

TokenProviderMiddleware

(that GitHub solution is a very good example)

Basically (to summarize some of that code):

You call something like the app.UseTokenProvider(); from the Configure (startup).

app.UseTokenProvider(_tokenProviderOptions);  
// or directly (w/o the extension method)
app.UseMiddleware<TokenProviderMiddleware>();
...

...and the TokenProviderMiddleware has a certain signature to satisfy (injecting the next in the chain into the .ctor and the public Task Invoke(HttpContext context)

This allows you to skip the controller, or as much as you'd like to do that.

Not sure if this solves your troubles but it helped me with something similar.

Get access token in middleware .net core 2.0

Add using Microsoft.AspNetCore.Authentication; to your middleware file.
That's the namespace of the AuthenticationTokenExtensions with the GetTokenAsync method.

asp.net core 2.0 Authorization is firing before authentication (JWT)

I'm also registering my authorization requirement and handler (note
these registrations happen AFTER I've configured authentication above,
and BEFORE I call serivces.AddMVC()

The order of services registration in DI container is not quite important. What is important it's the order of middleware registration in Startup.Configure method. You have not provided the code of this method, but I bet you add authentication middleware after MVC middleware or don't add it at all. Authentication middleware should be added before MVC, so make sure your Startup.Configure looks similar to this:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseAuthentication();
app.UseMvc();
}

Check following articles for more details:

  • ASP.NET Core Middleware -
    Ordering
  • Why UseAuthentication must be before UseMvc in NET Core
    2.0


Related Topics



Leave a reply



Submit