.NET Core Middleware – OWASP Headers Part 3 – Adding Unit Tests And More Headers

Jamie Taylor2 comments
OWASP Headers Part 3 header imageSource: https://unsplash.com/search/secure?photo=AmECs71KlSQ Copyright 2017: Mike Wilson

Today’s header image was created by Mike Wilson at Unsplash

Caveat

Just a quick note before we begin.

A caveat before we begin?

Hi everyone, this article series was written with .NET Core 1.x in mind. With the .NET Core 2.0 RTM (release to manufacturers) having just happened last week, and there being some breaking changes in 2.0, it’s worth noting that this article was written specifically for .NET Core 1.x.

Now that .NET Core 2.0 has been RTMd, I’ll release an updated version of this article series where I get around to upgrading the middleware to .NET Core 2.0. If you’ve found yourself here in the future (after 2.0 was released), I’ll add a link to the .NET Core 2.0 specific articles.

it seems pointless to write about upgrading the middleware until after I’ve finished the whole series

Anyway, back to the article.


This is part three in a multi-part series on creating an OWASP Middleware.

I’d really recommend you go back and read the previous parts, if you haven’t already.

In the first part, I talked about the basic make up of ASP.NET Core middleware and how to create a very basic one.

In the second part, I talked about how to deserialise a configuration object and use dependency injection to ensure that the configuration object is used in middleware.

if you want to skip all of this and take a look at the finished source code, then head over to the GitHub repo for it

The Plan

We’re almost finished with this middleware; we only have a few things left to do:

  • Setup the remainder of the header configuration object
  • Add the remainder of the header configuration values to the JSON file
  • Create an extension method for using the middleware, so we don’t have to use the UseMiddlware<> method

We’ll make the extension method first as this means looking into stuff specific to ASP.NET Core, whereas the other steps are standard C# stuff.

middleware Extension Method

Taking a look at our current Startup.cs

public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory,
IOptions<SecureHeadersMiddlewareConfiguration> secureHeaderSettings)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseMiddleware<SecureHeadersMiddleware>(secureHeaderSettings.Value);
app.UseMvc();
}

The call to inject the middleware into the pipeline uses the UseMiddleware<> extension method

which exists in the Microsoft.AspNetCore.Builder namespace

to inject our middleware. This works fine, but doesn’t read brilliantly because the developer of any consuming application needs to know and remember what this code block looks like. I want to simplify this, and we can with an extension method which will call the above extension method.

it’s extension methods all the way down

The benefit to doing this is that when later versions of ASP.NET Core come out with breaking changes to how middleware are consumed and created

of course there will be breaking changes; there always are

we only need to change the code in our extension method.

Creating the Extension Method

Open the OwaspHeaders.Core source code (i.e. not the example project which consumes it), create a file within the Extensions directory called SecureHeadersMiddlewareExtensions.cs and paste the following code into it:

using Microsoft.AspNetCore.Builder;
using OwaspHeaders.Core.Models;
namespace OwaspHeaders.Core.Extensions
{
/// <summary>
/// Extension methods to allow us to easily build the middleware
/// </summary>
public static class SecureHeadersMiddlewareExtensions
{
/// <summary>
/// Extention method to include the <see cref="SecureHeadersMiddleware" /> in
/// an instance of an <see cref="IApplicationBuilder" />.
/// This works in the same way was the MVC, Static files, etc. middleware
/// </summary>
/// <param name="builder">
/// The instance of the <see cref="IApplicationBuilder" /> to use
/// </param>
/// <param name="config">
/// An instance of the <see cref="SecureHeadersMiddlewareConfiguration" />
/// containing all of the config for each request
/// </param>
/// <returns>
/// The <see cref="IApplicationBuilder"/> with the <see cref="SecureHeadersMiddleware" />
/// added
/// </returns>
public static IApplicationBuilder UseSecureHeadersMiddleware(this IApplicationBuilder builder,
SecureHeadersMiddlewareConfiguration config)
{
return builder.UseMiddleware<SecureHeadersMiddleware>(config);
}
}
}

Most of the content of this file is made up of comments, but that can be quite useful for the consuming developer to see these comments via IntelliSense as they’re adding the middleware.

And you’ll notice that this extension method is just a wrapper around the current way that we’re consuming the middleware (in the code block above).

Consuming via the Extension Method

We can continue to consume the middleware using the UseMiddleware<> extension method (as in our example project) , or we could make it easier by using the extension method we just wrote.

Open the example project and head over to the Startup class. Making sure to restore all packages (either in the terminal or in your IDE) first, change the contents of the Configure method to read:

public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory,
IOptions<SecureHeadersMiddlewareConfiguration> secureHeaderSettings)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseSecureHeadersMiddleware(secureHeaderSettings.Value);
app.UseMvc();
}

We’ve extending our middleware in that we’ve added a class called SecureHeadersMiddlewareExtensions. However, we’ve used that class to add a method called UseSecureHeadersMiddleware which actually extends the IApplicationBuilder, which is what’s used in the Configure method to build out application.

Pretty smart, huh?

And if you look into things like MVC you’ll find that they, too, use a similar extension method.

in fact, here is the source code for how the MVC namespace does it.

Unit Tests

Before we can add the remaining headers, we need to add some unit tests

which we’ll break along the way

Otherwise, how do we know that the code actually does what we want it to. Other than actually consuming the middleware and testing the headers found in the requests, that is.

It would be better to have unit tests as we can quickly test the behaviour of the middleware without having to go through the steps of consuming it, spinning up a server, issuing some requests, and checking the responses.

which, I’ll grant you, is not really that hard

Plus, with unit tests we’ll be able to get our build server to run them after each commit and let us know if the build is broken.

assuming that someone committed without running the tests on their machine beforehand

Creating the Unit Test Class

I’ve written about unit tests before

here is a link to that (very early) article

Back then I used xUnit to show off how unit tests are written and run in .NET Core, and this article isn’t going to be any different.

in that our unit tests will be written with xUnit

You can use nUnit if you wish, the process will be pretty much the same, but some of the syntax will be a little different. I prefer writing xUnit tests, so I’m going to use those for my middleware tests, but you could use any unit testing framework you like.

the big three are: MSTest, xUnit and nUnit 

We’ll need to run some commands in the root directory of our code base (where the directories for both the middleware and example projects are):

dotnet new xunit --name tests --framework netcoreapp1.1

This will create an xunit project for us within a directory called tests. Next we need to cd into that directory and add a reference to the middleware:

cd tests
dotnet add reference OwaspHeaders.Core/OwaspHeaders.Core.csproj

Now we need to rename UnitTest1.cs to SecureHeadersInjectedTest.cs

UnitTest1.cs isn’t a great name at all, but it’s good enough for the template 😛

A lot of what we’re about to do is TDD, so we’re going to write some tests which might fail to start with.

Firstly, let’s write a passing test

because starting with failure is always for morale

by pasting the following code into the SecureHeadersInjectedTest.cs file:

using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using OwaspHeaders.Core;
using OwaspHeaders.Core.Models;
using Xunit;
namespace tests
{
public class SecureHeadersInjectedTest
{
private readonly Task _onNextResult = Task.FromResult(0);
private readonly RequestDelegate _onNext;
private readonly DefaultHttpContext _context;
private readonly SecureHeadersMiddlewareConfiguration _middlewareConfig;
public SecureHeadersInjectedTest()
{
_onNext = _ =>
{
Interlocked.Increment(ref _onNextCalledTimes);
return _onNextResult;
};
_context = new DefaultHttpContext();
_middlewareConfig = new SecureHeadersMiddlewareConfiguration();
}
[Fact]
public async Task Invoke_StrictTransportSecurityHeaderName_HeaderIsPresent()
{
// arrange
var secureHeadersMiddleware = new SecureHeadersMiddleware(_onNext, _middlewareConfig);
// act
await secureHeadersMiddleware.Invoke(_context);
// assert
if (_middlewareConfig.UseHsts)
{
Assert.True(_context.Response.Headers.ContainsKey(Constants.StrictTransportSecurityHeaderName));
Assert.Equal("max-age=31536000; includeSubDomains",
_context.Response.Headers[Constants.StrictTransportSecurityHeaderName]);
}
else
{
Assert.False(_context.Response.Headers.ContainsKey(Constants.StrictTransportSecurityHeaderName));
}
}
}
}

The first thing we do here is set up some private variables which we’ll use everywhere.

private readonly Task _onNextResult = Task.FromResult(0);
private readonly RequestDelegate _onNext;
private readonly DefaultHttpContext _context;
private readonly SecureHeadersMiddlewareConfiguration _middlewareConfig;
public SecureHeadersInjectedTest()
{
_onNext = _ =>
{
Interlocked.Increment(ref _onNextCalledTimes);
return _onNextResult;
};
_context = new DefaultHttpContext();
_middlewareConfig = new SecureHeadersMiddlewareConfiguration();
}

Remember that a middleware has a reference to the next item in the pipeline (which is what we’re doing with _onNext), we invoke the middleware on an HttpContext, and we need to pass an instance of the SecureHeadersMiddlewareConfiguration into the constructor for our middleware.

for the all important settings

[Fact]
public async Task Invoke_StrictTransportSecurityHeaderName_HeaderIsPresent()
{
// arrange
var secureHeadersMiddleware = new SecureHeadersMiddleware(_onNext, _middlewareConfig);
// act
await secureHeadersMiddleware.Invoke(_context);
// assert
if (_middlewareConfig.UseHsts)
{
Assert.True(_context.Response.Headers.ContainsKey(Constants.StrictTransportSecurityHeaderName));
Assert.Equal("max-age=31536000; includeSubDomains",
_context.Response.Headers[Constants.StrictTransportSecurityHeaderName]);
}
else
{
Assert.False(_context.Response.Headers.ContainsKey(Constants.StrictTransportSecurityHeaderName));
}
}

And here is out all important test. We’re using the AAA (Arrange, Act, Assert) pattern for our test and will use the same pattern for the remaining test that we write.

Running the Test

There’s two ways to run the tests:

  • in the terminal
  • in an IDE
Terminal

In the terminal, we just need to issue a single command. But before we can run that command, we need to add the package for the CLI Tool which that command runs.

To do that, add the following (highlighted) to the tests.csproj:

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.2" />
<ProjectReference Include="..\OwaspHeaders.Core\OwaspHeaders.Core.csproj" />
<PackageReference Include="xunit" Version="2.3.0-beta2-build3683" />
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.0-beta2-build3683" />
</ItemGroup>
</Project>

We’ve updated the version of the xunit package that we’re using to 2.3.0-beta and we’ve included the dotnet-xunit 2.3.0-beta command.

note: at the time of writing, these packages were in beta.

If you’re reading this in the future, it will be worth checking NuGet for the newest version of the CLI tool and for xunit

Once we’ve done that, we need to do a restore before we can run the test.

dotnet restore

Then we can run the test:

dotnet xunit

Which should produce something similar to the following as output:

Detecting target frameworks in tests.csproj...
Building for framework netcoreapp1.1...
OwaspHeaders.Core -> /Code/OwaspHeaders.Core/src/bin/Debug/netstandard1.4/OwaspHeaders.Core.dll
tests -> /Code/OwaspHeaders.Core/tests/bin/Debug/netcoreapp1.1/tests.dll
Running .NET Core tests for framework netcoreapp1.1...
xUnit.net Console Runner (64-bit .NET Core 4.6.25211.01)
Discovering: tests
Discovered: tests
Starting: tests
Finished: tests
=== TEST EXECUTION SUMMARY ===
tests Total: 1, Errors: 0, Failed: 0, Skipped: 0, Time: 0.176s

Most of this is diagnostic output. We can see that xUnit builds out projects, then discovers all of our tests and runs them.

This is the most important line of output:

tests Total: 1, Errors: 0, Failed: 0, Skipped: 0, Time: 0.176s

hooray!

IDE

If you remember in the previous article in this series, I hinted at my use of Rider

you can see that here

As such, I’m going to show how we can run the unit tests within Rider.

Before we can open the solution in Rider, we’ll need to create a solution file in the root directory:

cd ..
dotnet new sln --name OwaspHeaders.Core

assuming that you’re in the tests directory

Once you’ve done that, paste the following into the sln file:

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26124.0
MinimumVisualStudioVersion = 15.0.26124.0
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OwaspHeaders.Core", "OwaspHeaders.Core\OwaspHeaders.Core.csproj", "{BBABF075-EC10-4024-AD47-56E72A557DE8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "example", "example\example.csproj", "{C510C80A-ACF3-40DC-B4CB-D416B0B36772}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tests", "tests\tests.csproj", "{396F1657-B767-46E9-9DCD-9F1AF1B42609}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

Now we can open the solution with Rider.

Start Rider and click “Open Solution or Project”

Rider Splash Screen - Open Solution

Then select the new sln file:

Rider Splash Screen - Select Solution

From here, we’ll need to see the Unit Tests window. To do that, head to: View > Tool Windows > Unit Tests

Rider displaying the Unit Test Window
The more savvy of you will have noticed that it is already open in this screen shot

From here we can run the unit tests in a number of ways.

In the Unit Test window there’s a button to run the unit tests:

Rider Unit Tests Window - Run Unit Tests

Or we can right click the test project and select Run Unit Tests:

Rider - Right Click Run Test

Or we can right click on any of the Fact attributes in a test listing and select Run Unit Tests:

Rider - Run Unit Test in Code Listing

Either way, once the unit tests have been run the Unit Tests window will update with the results of the unit tests:

Rider - Unit Tests Complete

Filling in the Header Configuration Object

If you remember in the last article in this series, we only got as far as having a single header being represented in our configuration object. We’re about to rectify that.

We’ll tackle each header individually

which is how I went about writing the source code in the first place

with a section on each one, describing what’s going on in the POCOs and the BuildHeaderValue method for each one.

But first, a quick reminder to check out the OWASP Secure Headers Project as I’ll be referring to it throughout the following sections.

Public Key Pinning Extension for HTTP (HPKP) and X-Frame-Options

Head over to the source code for the middleware (in the OwaspHeaders.Core directory) and replace the contents of the ISecureHeadersMiddlewareConfiguration.cs file (found in the Models directory) with the the following code:

namespace OwaspHeaders.Core.Models
{
public interface ISecureHeadersMiddlewareConfiguration
{
bool UseHsts { get; set; }
bool UseHpkp { get; set; }
bool UseXFrameOptions { get; set; }
HstsConfiguration HstsConfiguration { get; set; }
HPKPConfiguration HpkpConfiguration { get; set; }
XFrameOptionsConfiguration XFrameOptionsConfiguration { get; set; }
}
}

When you save this file, you’ll get errors in the SecureHeadersMiddlewareConfiguration.cs, This is because it implements everything from ISecureHeadersMiddlewareConfiguration.cs, but is now missing some new properties. So let’s go fix that by pasting the following into it:

namespace OwaspHeaders.Core.Models
{
public class SecureHeadersMiddlewareConfiguration : ISecureHeadersMiddlewareConfiguration
{
public bool UseHsts { get; set; }
public bool UseHpkp { get; set; }
public bool UseXFrameOptions { get; set; }
public HstsConfiguration HstsConfiguration { get; set; }
public HPKPConfiguration HpkpConfiguration { get; set; }
public XFrameOptionsConfiguration XFrameOptionsConfiguration { get; set; }
public SecureHeadersMiddlewareConfiguration()
{
UseHsts = true;
UseHpkp = true;
UseXFrameOptions = true;
HstsConfiguration = new HstsConfiguration();
HpkpConfiguration = new HPKPConfiguration();
XFrameOptionsConfiguration = new XFrameOptionsConfiguration();
}
}
}

Yet more errors

oh no

Create a file (in the Models directory) called HPKPConfiguration.cs and paste the following code into it:

using System.Collections.Generic;
using System.Text;
namespace OwaspHeaders.Core.Models
{
public class HPKPConfiguration : IConfigurationBase
{
public List<string> PinSha256 { get; set; }
public int MaxAge { get; set; }
public bool IncludeSubDomains { get; set; }
public string ReportUri { get; set; }
public HPKPConfiguration()
{
PinSha256 = new List<string>();
ReportUri = "http://example.com/pkp-report";
MaxAge = 10000;
IncludeSubDomains = true;
}
public string BuildHeaderValue()
{
var stringBuilder = new StringBuilder();
foreach (var pinSha256 in PinSha256)
{
stringBuilder.Append("pin-sha256=\"");
stringBuilder.Append(pinSha256);
stringBuilder.Append("\";");
}
stringBuilder.Append("report-url=\"");
stringBuilder.Append(ReportUri);
stringBuilder.Append("\";max-age=");
stringBuilder.Append(MaxAge);
stringBuilder.Append(IncludeSubDomains ? "; includeSubDomains" : string.Empty);
return stringBuilder.ToString();
}
}
}

Only one more error to fix. Create a file called XFrameOptionsConfiguration.cs in the same directory (Models) and paste the following code into it:

using System;
using System.Text;
using OwaspHeaders.Core.Helpers;
namespace OwaspHeaders.Core.Models
{
public class XFrameOptionsConfiguration : IConfigurationBase
{
public enum XFrameOptions { deny, sameorigin, allowfrom };
public XFrameOptions OptionValue { get; set; }
public string AllowFromDomain { get; set; }
public XFrameOptionsConfiguration()
{
OptionValue = XFrameOptions.sameorigin;
}
public string BuildHeaderValue()
{
var stringBuilder = new StringBuilder();
switch (OptionValue)
{
case XFrameOptions.deny:
stringBuilder.Append("deny");
break;
case XFrameOptions.sameorigin:
stringBuilder.Append("sameorigin");
break;
case XFrameOptions.allowfrom:
if (string.IsNullOrWhiteSpace(AllowFromDomain))
{
ArgumentExceptionHelper.RaiseException(nameof(AllowFromDomain));
}
stringBuilder.Append("allow-from: ");
stringBuilder.Append(AllowFromDomain);
break;
}
return stringBuilder.ToString();
}
}
}
What We Just Did

The HPKP Configuration class is the more complex of the two classes which we added:

using System.Collections.Generic;
using System.Text;
namespace OwaspHeaders.Core.Models
{
public class HPKPConfiguration : IConfigurationBase
{
public List<string> PinSha256 { get; set; }
public int MaxAge { get; set; }
public bool IncludeSubDomains { get; set; }
public string ReportUri { get; set; }
public HPKPConfiguration()
{
PinSha256 = new List<string>();
ReportUri = "http://example.com/pkp-report";
MaxAge = 10000;
IncludeSubDomains = true;
}
public string BuildHeaderValue()
{
var stringBuilder = new StringBuilder();
foreach (var pinSha256 in PinSha256)
{
stringBuilder.Append("pin-sha256=\"");
stringBuilder.Append(pinSha256);
stringBuilder.Append("\";");
}
stringBuilder.Append("report-url=\"");
stringBuilder.Append(ReportUri);
stringBuilder.Append("\";max-age=");
stringBuilder.Append(MaxAge);
stringBuilder.Append(IncludeSubDomains ? "; includeSubDomains" : string.Empty);
return stringBuilder.ToString();
}
}
}

We’ve taken the properties of the HPKP header, and provided a list of default values:

public HPKPConfiguration()
{
PinSha256 = new List<string>();
ReportUri = "http://example.com/pkp-report";
MaxAge = 10000;
IncludeSubDomains = true;
}

As you can see, we’re not supplying and Pin SHA-256 keys in the constructor, so we’ll need to ensure that we have something useful when we consume this middleware.

more on that in a moment

Whereas the X-Frame-Options Configuration class isn’t as complex, as it’s based on the simpler X-Frame-Options header.

Adding More Unit Tests

Looking at the SecureHeadersInjectedTest.cs file (in the tests project), we need to add some tests for the new headers.

Add the following code to the SecureHeadersInjectedTest.cs file:

[Fact]
public async Task Invoke_PublicKeyPinsHeaderName_HeaderIsPresent()
{
// arrange
var secureHeadersMiddleware = new SecureHeadersMiddleware(_onNext, _middlewareConfig);
// act
await secureHeadersMiddleware.Invoke(_context);
// assert
if (_middlewareConfig.UseHpkp)
{
Assert.True(_context.Response.Headers.ContainsKey(Constants.PublicKeyPinsHeaderName));
Assert.Equal("report-url=\"http://example.com/pkp-report\";max-age=10000; includeSubDomains",
_context.Response.Headers[Constants.PublicKeyPinsHeaderName]);
}
else
{
Assert.False(_context.Response.Headers.ContainsKey(Constants.PublicKeyPinsHeaderName));
}
}
[Fact]
public async Task Invoke_XFrameOptionsHeaderName_HeaderIsPresent()
{
// arrange
var secureHeadersMiddleware = new SecureHeadersMiddleware(_onNext, _middlewareConfig);
// act
await secureHeadersMiddleware.Invoke(_context);
// assert
if (_middlewareConfig.UseXFrameOptions)
{
Assert.True(_context.Response.Headers.ContainsKey(Constants.XFrameOptionsHeaderName));
Assert.Equal("sameorigin", _context.Response.Headers[Constants.XFrameOptionsHeaderName]);
}
else
{
Assert.False(_context.Response.Headers.ContainsKey(Constants.XFrameOptionsHeaderName));
}
}

As with the previous test, we’re performing slightly different assertions whether the configuration object states whether each header should be present.

These tests should fail when you run them. So let’s make them pass.

Fixing the Failing Tests

This is surprisingly easy to do. We did most of the hard work last time

psst. Click here to go to the specific section

All we need to do is alter the SecureHeadersMiddleware.cs file (in the OwaspHeaders.Core directory) to add the two new headers to the HttpContext’s Response Header property:

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using OwaspHeaders.Core.Extensions;
using OwaspHeaders.Core.Models;
namespace OwaspHeaders.Core
{
/// <summary>
/// A middleware for injecting OWASP recommended headers into a
/// HTTP Request
/// </summary>
public class SecureHeadersMiddleware
{
private readonly RequestDelegate _next;
private readonly SecureHeadersMiddlewareConfiguration _config;
public SecureHeadersMiddleware(RequestDelegate next, SecureHeadersMiddlewareConfiguration config)
{
_next = next;
_config = config;
}
/// <summary>
/// The main task of the middleware. This will be invoked whenever
/// the middleware fires
/// </summary>
/// <param name="HttpContext">The <see cref="HttpContext" /> for the current request or response</param>
/// <returns></returns>
public async Task Invoke(HttpContext httpContext)
{
if (_config == null)
{
throw new ArgumentException($@"Expected an instance of the
{nameof(SecureHeadersMiddlewareConfiguration)} object.");
}
if (_config.UseHsts)
{
httpContext.TryAddHeader(Constants.StrictTransportSecurityHeaderName,
_config.HstsConfiguration.BuildHeaderValue());
}
if (_config.UseHpkp)
{
httpContext.TryAddHeader(Constants.PublicKeyPinsHeaderName,
_config.HpkpConfiguration.BuildHeaderValue());
}
if (_config.UseXFrameOptions)
{
httpContext.TryAddHeader(Constants.XFrameOptionsHeaderName,
_config.XFrameOptionsConfiguration.BuildHeaderValue());
}
// Call the next middleware in the chain
await _next.Invoke(httpContext);
}
}
}

The new code (highlighted) works in the same way as the code for the HTTP Strict Transport Security header.

Running the unit tests again, we should see all three of them pass:

cd tests
dotnet xunit
Detecting target frameworks in tests.csproj...
Building for framework netcoreapp1.1...
OwaspHeaders.Core -> /Code/OwaspHeaders.Core/src/bin/Debug/netstandard1.4/OwaspHeaders.Core.dll
tests -> /Code/OwaspHeaders.Core/tests/bin/Debug/netcoreapp1.1/tests.dll
Running .NET Core tests for framework netcoreapp1.1...
xUnit.net Console Runner (64-bit .NET Core 4.6.25211.01)
Discovering: tests
Discovered: tests
Starting: tests
Finished: tests
=== TEST EXECUTION SUMMARY ===
tests Total: 3, Errors: 0, Failed: 0, Skipped: 0, Time: 0.193s
Consuming the New Headers

Now that we’ve added code to the middleware which will generate the new headers, we need to alter our example project to consume them.

Well, we don’t really need to alter it to consume them at all. But we’re using the example project as a way of testing the consumption of the headers. Which is why we created the secureHeaderSettings.json file (which we’re using to override the default values in the constructor for the SecureHeadersMiddlewareConfiguration.cs class.

Head over to the secureHeaderSettings.json file (in the example project directory) and replace it’s content with the following:

{
"SecureHeadersMiddlewareConfiguration": {
"UseHsts": "true",
"HstsConfiguration": {
"MaxAge": 63072000,
"IncludeSubDomains": "true"
},
"UseHpkp": "true",
"HPKPConfiguration" :{
"PinSha256" : [
"e927fad33f9eb96126896413502a1034be0ca379dec377fb891feb9ebc720e47"
],
"MaxAge": 3,
"IncludeSubDomains": "true",
"ReportUri": "https://github.com/GaProgMan/OwaspHeaders.Core"
},
"UseXFrameOptions": "true",
"XFrameOptionsConfiguration": {
"OptionValue": "deny"
}
}
}

Now if you run the example application

remember, it’s a webapi application

you should get a response which contains all of the OWASP headers we’ve implemented so far.

OWASP Headers so far
How cool is this?

We’ll add the remaining headers, tests, consume them, and set them up with json configuration in the next

and final

article in this series.

Conclusion

We’ve refactored our middlwware and created an extension method to make it easier to consume it.

We’ve added unit tests so that we can ensure that our middleware does what it’s supposed to do, and we’ve seen how we can run those tests from the terminal or from within Rider

the hot new IDE, that is

We created these unit tests using the TDD principles, and seen how to use the AAA (Arrange, Act, Assert) pattern with a unit test framework.

We’ve then consumed the new headers, both in the unit tests and our example project.


We’re quickly coming to the end of this particular series of blog posts. Have you created any ASP.NET Core middleware yet? Are you using TDD? Do you prefer xUnit over nUnit?

Let me know in the comments and let’s keep the conversation going.

If this post has helped you out, please consider   Buy me a coffeeBuying me a coffee
Jamie Taylor
A .NET developer specialising in ASP.NET MVC websites and services, with a background in WinForms and Games Development. When not programming using .NET, he is either learning about .NET Core (and usually building something cross platform with it), speaking Japanese to anyone who'll listen, learning about languages, writing for his non-dev blog, or writing for a blog about video games (which he runs with his brother)