Before we add more headers, let’s talk about what’s wrong with the above code. Specifically, we need to figure out what’s wrong with the following block:
public async Task Invoke(HttpContext httpContext) { httpContext.Response.Headers.Add("Strict-Transport-Security", "max-age=31536000; includeSubDomains"); // Call the next middleware in the chain await _next.Invoke(httpContext); }“Wrong” isn’t the correct word to use. There are two issues here, see if you can spot what they are.
psst. Take a look atthis section from the previous article in this series
The first thing that should jump out at you is the potential for an ArgumentException .
ArgumentExceptionThe HttpContext’s Response Headers property is a Dictionary type, and you can’t have multiple instances of the same key in a Dictionary.
you can read through the source for the headers property here
When you try to insert a duplicate key into a Dictionary in C#, you get an ArgumentException.
Check the Microsoft documentation for a description of what happens when a duplicate key is added to a dictionary .
To get around this, what we need to do is check whether our header exists in the Headers dictionary before we add it. Luckily all C# dictionaries expose a method called “ContainsKey” which takes a value and checks whether the dictionary contains that key.
talk about “does what it says on the tin”
We can do this by changing the above code to match the following:
// basic headers check to avoid potential ArgumentException public async Task Invoke ( HttpContext httpContext ) { if (!httpContext.Response.Headers.ContainsKey( " Strict-Transport-Security " ) { httpContext.Response.Headers.Add( " Strict-Transport-Security " , " max-age=31536000; includeSubDomains " ); } // Call the next middleware in the chain await _next.Invoke(httpContext); }That will do perfectly.
Except that we’ll end up copy pasting this check multiple times as we add more headers. We also have to type the name of the header twice. Let’s fix the header name issue first.
ConstantsSince the header names are constant (they’re not likely to ever change, seeing as how they’re ratified against a standard), we can make them constants in our application.
Create a new file in the OwaspHeaders.Core source directory (along side the csproj) called Constants.cs and paste the following code into it:
namespace OwaspHeaders.Core { public static class Constants { public static readonlystring StrictTransportSecurityHeaderName = "Strict-Transport-Security"; } }we’ll add to this list of constants in future parts of this series
Adding and consuming these constants means that we can simplify our header check:
// basic headers check to avoid potential ArgumentException public async Task Invoke ( HttpContext httpContext ) { if (!httpContext.Response.Headers.ContainsKey(Constants.StrictTransportSecurityHeaderName) { httpContext.Response.Headers.Add(Constants.StrictTransportSecurityHeaderName, " max-age=31536000; includeSubDomains " ); } // Call the next middleware in the chain await _next.Invoke(httpContext); }Now let’s do something about that header check using an extension method.
Extension MethodsFor those who don’t know what extension methods are:
Extension methods enable you to “add” methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. Extension methods are a special kind of static method, but they are called as if they were instance methods on the extended type
Source: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods
If you’ve ever used LINQ, then you’ve used extension methods. They’re extremely useful, and we’re about to make one for the HttpContext object.
Create a directory in the OwaspHeaders.Core source directory (along side the csproj) called Extensions, then a file within it calledHttpContextExtensions.cs and paste the following code into it:
using System; using Microsoft.AspNetCore.Http; namespace OwaspHeaders.Core.Extensions { public static class HttpContextExtensions { public static bool ResponseContainsHeader(this HttpContext httpContext, string header) { return httpContext.Response.Headers.ContainsKey(header); } public static bool TryAddHeader(this HttpContext httpContext, string headerName, string headerValue) { if (httpContext.ResponseContainsHeader(headerName)) return true; try { httpContext.Response.Headers.Add(headerName, headerValue); return true; } catch (ArgumentException) { return false; } } } }We’ll make use of TryAddHeader in a moment, but a quick look at how it works.
public static bool TryAddHeader(this HttpContext httpContext, string headerName, string headerValue) { if (httpContext.ResponseContainsHeader(headerName)) return true; try { httpContext.Response.Headers.Add(head