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

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”
Then select the new sln file:
From here, we’ll need to see the Unit Tests window. To do that, head to: View > Tool Windows > Unit Tests

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:
Or we can right click the test project and select Run Unit Tests:
Or we can right click on any of the Fact attributes in a test listing and select Run Unit Tests:
Either way, once the unit tests have been run the Unit Tests window will update with the results of the unit tests:
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.

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.