How To Migrate Your Authentication Middleware To ASP.NET Core 2.0?

Did you give ASP.NET Core 2.0 a try? If no, you should :) because there are a lot of cool stuff there!
There are also several breaking changes, especially on the security part. Indeed here is an official documentation about how to migrate authentication and identity to ASP.NET Core 2.0.

How we defined a custom authentication process in ASP.NET Core 1.*?

Months ago, I blogged about how to deal with identiy when testing an ASP.NET Core application. In short, I explained how to create your own authentication middleware to control authentication and identity within the test context to validate the different authorization scenarios.
Here is a summary of how we could create an authentication middleware in ASP.NET Core 1.*.

  • Define authentication options
  • Define an authentication handler
  • Define a middleware which would be called within the request pipeline and which would used the handler with the options to authenticate it.

Reminder:

public class CustomAuthenticationOptions : AuthenticationOptions  
{
    public CustomAuthenticationOptions()
    {
       this.AuthenticationScheme = "CustomScheme";
    }
}

public class CustomAuthenticationHandler : AuthenticationHandler<CustomAuthenticationOptions>  
{
    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        return Task.FromResult(
            AuthenticateResult.Success(
                new AuthenticationTicket(
                    new ClaimsPrincipal(Options.Identity),
                    new AuthenticationProperties(),
                    this.Options.AuthenticationScheme)));
    }
}

public class CustomAuthenticationMiddleware : AuthenticationMiddleware<CustomAuthenticationOptions>  
{
    private readonly RequestDelegate next;

    public CustomAuthenticationMiddleware(RequestDelegate next, IOptions<CustomAuthenticationOptions> options, ILoggerFactory loggerFactory)
        : base(next, options, loggerFactory, UrlEncoder.Default)
    {
        this.next = next;
    }

    protected override AuthenticationHandler<CustomAuthenticationOptions> CreateHandler()
    {
        return new CustomAuthenticationHandler();
    }
}

Then, you could configure your pipeline to use your middleware inside the Startup class:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)  
{
    ...
    app.use<CustomAuthenticationMiddleware>();
}

How to do it in ASP.NET Core 2.0?

In the last version of ASP.NET Core, you cannot use the AuthenticationMiddleware class anymore. In fact the class still exists and it is still visible but it is not generic anymore and is used internally by the different authentication providers.
So how to define our own authentication process? There is not much documentation about it so I took a look to the ASP.NET Core 2.0 authentication providers code (the Jwtbearer provider for instance) to understand the new model.
It is all about the authentication scheme. It works like an identifier for your authentication process. So now the main idea is to configure an authentication scheme and define how to handle it:

1 - Define authentication options using the AuthenticationSchemeOptions class. For demonstration purposes, here the options contain directly a claims based identity:

public class CustomAuthenticationOptions : AuthenticationSchemeOptions  
{
    public ClaimsIdentity Identity { get; set; }
    // ... Other authentication options properties
}

2 - Define an authentication handler. The HandleAuthenticateAsync method is the entry point of your authentication process. It is where you have to implement your authentication logic. For demonstration purposes, here we just make the authentication succeed all the time by creating a claims based principal using the identity provided by the authentication scheme options (see previous point):

public class CustomAuthenticationHandler : AuthenticationHandler<CustomAuthenticationOptions>  
{
    public CustomAuthenticationHandler(IOptionsMonitor<CustomAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
        : base(options, logger, encoder, clock) { }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        return Task.FromResult(
             AuthenticateResult.Success(
                new AuthenticationTicket(
                    new ClaimsPrincipal(Options.Identity),
                    new AuthenticationProperties(),
                    this.Scheme.Name)));
        }
    }

You can note one slight difference here with the previous version: the authentication scheme information are now part of the AuthenticationHandler class so you do not need to include them in the authentication options anymore.

3 - Create an extension method for the AuthenticationBuilder class to register your authentication scheme. This scheme will represent your custom authentication process. By registering it, we will also define how to handle it:

public static class CustomAuthenticationExtensions  
{
    public static AuthenticationBuilder AddCustomAuthentication(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<CustomAuthenticationOptions> configureOptions)
    {
        return builder.AddScheme<CustomAuthenticationOptions, CustomAuthenticationHandler>(authenticationScheme, displayName, configureOptions);
    }
}

4 - Setting up authentication services using your authentication scheme within the ConfigureServices method of the application Startup class:

public void ConfigureServices(IServiceCollection services)  
{
    ...
    services.AddAuthentication(o =>
    {
        o.DefaultScheme = "MyCustomScheme"
    }).AddCustomAuthentication("MyCustomScheme", "This is my lovely authentication scheme", o => { });
    ...
}

Note here that we define our scheme as the default scheme. It means, it is the scheme that will be used by default if no one is explicitly defined.

You now have a ASP.NET Core 2.0 working custom authentication process! Have fun ;)