Custom Authentication in ASP.NET Core 2.0

This blog post shows a quick example of implementing custom authentication in .NET Core 2.0 to secure your Web API. And in most cases you do want to secure your Web APIs, even though they were internal (micro)services only. If you don’t have a proper Identity provider e. g. Identity Server in place, and just need a quick solution, then a simple option is to validate Authorization header against a hardcoded value.

To illustrate such implementation, let’s create a new Web API project (I am using Visual Studio 2017 Preview), and test if the default GET endpoint created returns values correctly. I use Postman for RESTful API testing. The GET call should be successful.

Custom Authentication - Postman no authentication

Now let’s enable authentication on all MVC endpoints by using an authorization filter in ConfigureServices() method. This way all newly added endpoints will be secure by default, and other developers won’t forget to manually secure the endpoints they are adding.


services.AddMvc(options =>
{
    // All endpoints need authentication
    options.Filters.Add(new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build()));
});

In Configure() method enable authentication capabilities.


// Enable authentication capabilities
app.UseAuthentication(); 

And then configure default authentication scheme and call an extension method for custom authentication that we are about to implement. Just add following in ConfigureServices() method.


// Add authentication 
services.AddAuthentication(options =>
{
	options.DefaultAuthenticateScheme = CustomAuthOptions.DefaultScheme;
	options.DefaultChallengeScheme = CustomAuthOptions.DefaultScheme;
})
// Call custom authentication extension method
.AddCustomAuth(options =>
{
	// Configure password for authentication
	options.AuthKey = "custom auth key"; 
});

The extension method is the key point to implementing the custom authentication handler. It adds a new authentication scheme with custom authentication handler and it’s options.

public static class AuthenticationBuilderExtensions
{
	// Custom authentication extension method
	public static AuthenticationBuilder AddCustomAuth(this AuthenticationBuilder builder, Action<CustomAuthOptions> configureOptions)
	{
		// Add custom authentication scheme with custom options and custom handler
		return builder.AddScheme<CustomAuthOptions, CustomAuthHandler>(CustomAuthOptions.DefaultScheme, configureOptions);
	}
}

And stubbing options and handler should allow to compile the code and run a quick test. Make sure handler’s stub returns “AuthenticateResult.Fail(“Not implemented”);” instead of throwing an exception :)

Custom Authentication - Postman unauthorized

Now the same endpoint returns 401 unauthorized response code. This is the expected result, since we don’t have our custom authentication handler’s logic implemented.

So now we just need to implement the custom authentication handler. The logic should be very simple – compare Authorization header value against the one passed in the AddCustomAuth() with options.


protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
	// Get Authorization header value
	if (!Request.Headers.TryGetValue(HeaderNames.Authorization, out var authorization))
	{
		return Task.FromResult(AuthenticateResult.Fail("Cannot read authorization header."));
	}

	// The auth key from Authorization header check against the configured one
	if (authorization != Options.AuthKey)
	{
		return Task.FromResult(AuthenticateResult.Fail("Invalid auth key."));
	}

	// Create authenticated user
	var identities = new List<ClaimsIdentity> {new ClaimsIdentity("custom auth type")};
	var ticket = new AuthenticationTicket(new ClaimsPrincipal(identities), Options.Scheme);

	return Task.FromResult(AuthenticateResult.Success(ticket));
}

Now run the Web API project again, add Authorization header in Postman, and try to hit the GET endpoint.

Custom Authentication - Postman authenticated

And if you removed the Authorization header, or used a different value, you would get 401 unauthorized response code again. All set!

Several hints and gotchas

  • Change logging level to Information, so you would see what authentication is doing behind the scenes.
  • Make sure ClaimsIdentity() returns an authenticated user, e.g. pass in an authentication type parameter.
  • Make sure you use the same scheme name across the application, e.g. use a constant.
  • If you have multiple endpoints and looking for an easier way to test them, try Swagger, an example of using it can be found here.

UPDATE (3rd December, 2017)

Per request on GitHub I’ve added multiple authentication keys support to the code example. I’ve used StringValues type for Options.AuthKey to keep the change backwards compatible, and to enable multiple authentication keys configuration. Therefore, both configuration examples provided below are valid now.

// Single Auth key
.AddCustomAuth(options =>
{
	options.AuthKey = "custom auth key";
});

// Multiple auth keys
.AddCustomAuth(options =>
{
	options.AuthKey = new[] { "custom auth key", "custom auth key2" };
});

Also, if multiple authorization headers are sent, all of them have to match the configured ones, i.e. if at least one key didn’t match, then 401 response will be returned.

 

Source code of the working custom authentication WebAPI is on my GitHub.

  • Tom Hancock

    Great article thanks – its hard to find quality examples on the latest .net core 2.0 features. I have just implemented your approach as part of our integration testing using TestServer. Implementing custom authentication for our test environment, replacing Identity authentication we use in Production.

  • Amir Shitrit

    Thank you so much!
    A very helpful post.

  • Сергей Коренюгин

    Thanks a lot!
    This article saved much time for me.