How to use NetEscapades.​AspNetCore.​SecurityHeaders

By FoxLearn 1/3/2025 9:59:06 AM   154
NetEscapades.AspNetCore.SecurityHeaders is a .NET library that helps you easily set HTTP security headers in ASP.NET Core applications to improve security by protecting against various types of attacks (like XSS, clickjacking, etc.).

What are security headers?

Security headers are HTTP headers that improve your application's security by guiding browsers to activate or deactivate specific features, thus minimizing your attack surface. While some headers apply to all HTTP responses, others are specifically for HTML responses. However, applying HTML-specific headers to all responses can be a good defense-in-depth strategy, as suggested by OWASP.

The NetEscapades.AspNetCore.SecurityHeaders package helps simplify the process of setting up security headers in ASP.NET Core applications. It offers sensible default configurations and a fluent builder pattern for easy customization to fit your application's needs.

First, add the package to your application:

Using .NET CLI:

dotnet add package NetEscapades.AspNetCore.SecurityHeaders --version 1.0.0-preview.1

Alternatively, add the package directly to your .csproj file:

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <!-- Add the package -->
    <PackageReference Include="NetEscapades.AspNetCore.SecurityHeaders" Version="1.0.0-preview.1" />
  </ItemGroup>
</Project>

Add the Security Headers Middleware

Configure the middleware by adding it to the services and middleware pipeline in your Program.cs or Startup.cs file.

You can add the security headers middleware to the start of your application's middleware pipeline using the UseSecurityHeaders() extension method as shown below.

var builder = WebApplication.CreateBuilder();

var app = builder.Build();

// Apply security headers
app.UseSecurityHeaders();

app.MapGet("/", () => "Hello world!");

app.Run();

Security Headers Provided by Default

The SecurityHeadersMiddleware automatically adds several important security headers to all HTTP responses. By default, the following headers will be applied:

  • X-Content-Type-Options: nosniff
  • X-Frame-Options: Deny
  • Referrer-Policy: strict-origin-when-cross-origin
  • Content-Security-Policy: object-src 'none'; form-action 'self'; frame-ancestors 'none'
  • Cross-Origin-Opener-Policy: same-origin
  • Strict-Transport-Security: max-age=31536000; includeSubDomains (applied only to HTTPS responses)

Customizing Content-Security-Policy (CSP)

You might want to allow certain external domains or disable certain types of scripts.

builder.Services.AddSecurityHeaders(options =>
{
    options.AddContentSecurityPolicy(builder =>
    {
        builder
            .AddDefaultSrc("'self'")
            .AddScriptSrc("'self'", "https://foxlearn.cdn.com")
            .AddStyleSrc("'self'", "'unsafe-inline'");
    });
});

This example allows scripts to be loaded from your own domain and from https://foxlearn.cdn.com, while allowing inline styles.

Customizing Security Headers

To customize security headers, you can create a HeaderPolicyCollection and use a fluent builder interface to adjust the default headers. For example, you can individually add default headers or remove the Server header. After creating a custom collection, apply it with the UseSecurityHeaders() method.

var policyCollection = new HeaderPolicyCollection()
    .AddFrameOptionsDeny()
    .AddContentTypeOptionsNoSniff()
    .AddStrictTransportSecurityMaxAgeIncludeSubDomains(maxAgeInSeconds: 60 * 60 * 24 * 365)
    .AddReferrerPolicyStrictOriginWhenCrossOrigin()
    .RemoveServerHeader()
    .AddContentSecurityPolicy(builder =>
    {
        builder.AddObjectSrc().None();
        builder.AddFormAction().Self();
        builder.AddFrameAncestors().None();
    })
    .AddCustomHeader("X-My-Test-Header", "Header value");

app.UseSecurityHeaders(policyCollection);

Applying Headers to Different Endpoints

A key feature in this release is the ability to apply different security headers to different endpoints:

  • Configure default and named policies for the application.
  • Use UseSecurityHeaders() to add middleware.
  • Apply specific policies to certain endpoints using the WithSecurityHeadersPolicy() method.

For example, you could apply API-specific headers to a /api endpoint:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

// 1. Configure the policies for the application
builder.Services.AddSecurityHeaderPolicies()
  .SetDefaultPolicy(p => p.AddDefaultSecurityHeaders()) //  Configure the default policy
  .AddPolicy("API", p => p.AddDefaultApiSecurityHeaders()); // Configure named policies

var app = builder.Build();

// 2. Add the security headers middleware
app.UseSecurityHeaders();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();

app.MapRazorPages();
app.MapGet("/api", () => "Hello world")
  .WithSecurityHeadersPolicy("API"); // 3. Apply a named policy to the endpoint

app.Run();

Complete Customization with SetPolicySelector

A new SetPolicySelector() method allows complete customization of headers based on the request. This is useful for scenarios like multi-tenant applications where headers vary based on the tenant.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSecurityHeaderPolicies()
  .SetPolicySelector((PolicySelectorContext ctx) =>
  {
      // TODO: anything you need to build the HeaderPolicyCollection
      // e.g. use services from the DI container (if you need to)
      IServiceProvider services = ctx.HttpContext.RequestServices; 

      var selector = services.GetService<TenantHeaderPolicyCollectionSelector>();
      var tenant = services.GetService<ITenant>();

      HeaderPolicyCollection policy = selector.GetPolicyForTenant(tenant);
      return policy; // This is the policy that is applied
  });

var app = builder.Build();

app.UseSecurityHeaders();
app.MapGet("/api", () => "Hello world");
app.Run();

The SetPolicySelector() method accepts a lambda or method that receives a PolicySelectorContext with the following information to help decide which security policy to apply:

  • HttpContext: The current HttpContext for the request.
  • ConfiguredPolicies: A dictionary of named policies that have been configured for the application.
  • DefaultPolicy: The default policy to apply to the request.
  • EndpointPolicyName: The name of the specific policy applied to the endpoint, if any.
  • EndpointPolicy: The policy applied to the endpoint, or null if no specific policy is applied.
  • SelectedPolicy: The policy that would be applied by default either EndpointPolicy or DefaultPolicy.

This context provides all the necessary details to select or customize the appropriate header policy for each request.

With endpoint-specific policies and the customization options in SetPolicySelector(), users can now easily tailor security header policies for different requests, without needing to modify the library's internals.

If you want to restore the "document header" functionality, you can recreate it using SetPolicySelector().

var builder = WebApplication.CreateBuilder(args);

// The mime types considered "documents"
string[] documentTypes = [ "text/html", "application/javascript", "text/javascript" ];
var documentPolicy = new HeaderPolicyCollection().AddDefaultSecurityHeaders();

builder.Services.AddSecurityHeaderPolicies()
  .SetDefaultPolicy(p => p.AddDefaultApiSecurityHeaders())
  .SetPolicySelector(ctx =>
  {
      // If the response is one of the "document" types...
      if (documentTypes.Contains(ctx.HttpContext.Response.ContentType))
      {
          // ... then return the "document" policy
          return documentPolicy;
      }

      // Otherwise return the original selected policy
      return ctx.SelectedPolicy;
  });

var app = builder.Build();

app.UseSecurityHeaders();
app.MapGet("/api", () => "Hello world");
app.Run();

Adding security headers to HTTP responses helps strengthen your application against attacks. NetEscapades.AspNetCore.SecurityHeaders simplifies this process and has recently undergone significant updates.