The ASP.NET Core security headers guide

TOC

Three years ago I wrote a tutorial about security headers in ASP.NET MVC. A lot happened since then and ASP.NET Core is the framework everyone should be on eventually. Time for an updated version for Core! This post is part of the series ASP.NET Security.

The ASP.NET Core security headers guide

I'll try to recap the different security headers in the post. If you are interested in more context, check out the original post. I'll go through each header like in the last post, but let's start by discussing how to modify headers in ASP.NET Core. Like ASP.NET (MVC) there are multiple ways of modifying headers. This post introduces two different ways:

  1. Through middleware
  2. In web.config

Headers in middleware

This is my favorite. Specifying headers in middleware can be done in C# code by creating one or more pieces of middleware. Most examples in this post will use this approach. In short, you either create a new middleware class or call the Use method directly in the Configure method in Startup.cs:

app.Use(async (context, next) =>
{
    context.Response.Headers.Add("Header-Name", "Header-Value");
    await next();
};

The code adds a new header named Header-Name to all responses. It's important to call the Use method before calling UseEndpoints, UseMvc, and similar.

A quick word about adding headers in middleware while also using the UseStatusCodePagesWithReExecute method. UseStatusCodePagesWithReExecute executes the configured error page within the same HTTP context as the original request. This means that your header adding middleware is executed twice. In this setup, make sure to check Headers to avoid exceptions:

if (!context.Response.Headers.ContainsKey("Header-Name"))
{
    context.Response.Headers.Add("Header-Name", "Header-Value");
}

Headers in web.config

ASP.NET Core no longer needs a web.config file. But since most people host their ASP.NET Core website on IIS anyway, a web.config file is still perfectly valid. While the system.web, appsettings, connectionstrings, and other root elements no longer apply the system.webServer element is for the web servers' eyes only.

Custom headers in ASP.NET Core can be added by adding a web.config file to the root of the website directory and including the following code:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <httpProtocol>
      <customHeaders>
        <add name="Header-Name" value="Header-Value" />
      </customHeaders>
    </httpProtocol>
  </system.webServer>
</configuration>

The configuration does the same as we saw with the middleware code. Adds a value named Header-Name. In some cases, you will need to use the web.config approach to remove headers. I'll guide you through in a moment.

Headers

X-Frame-Options

Hackers iframe your website to trick users into clicking unintended links. The X-Frame-Options tell any client that framing isn't allowed. The header can be easily added using middleware:

context.Response.Headers.Add("X-Frame-Options", "DENY");

Change the value to SAMEORIGIN to allow your site to iframe pages.

Blog posts throughout the web mention that the X-Frame-Options header is automatically added with the value SAMEORIGIN when enabling anti-forgery:

services.AddAntiforgery();

Either I'm doing something wrong or that feature was removed in recent versions of ASP.NET Core. I don't see the header automatically being added. In any case, if you want full control of the header, make sure to disable the automatic feature:

services.AddAntiforgery(options =>
{
    options.SuppressXFrameOptionsHeader = true;
});

X-Xss-Protection

The X-Xss-Protection header will cause most modern browsers to stop loading the page when a cross-site scripting attack is identified. The header can be added through middleware:

context.Response.Headers.Add("X-Xss-Protection", "1; mode=block");

The value 1 means enabled and the mode of block will block the browser from rendering the page.

X-Content-Type-Options

MIME-type sniffing is an attack where a hacker tries to exploit missing metadata on served files. The header can be added in middleware:

context.Response.Headers.Add("X-Content-Type-Options", "nosniff");

The value of nosniff will prevent primarily old browsers from MIME-sniffing.

If you want to cover static files as well, the header can be added to the web.config file instead:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <httpProtocol>
      <customHeaders>
        <add name="X-Content-Type-Options" value="nosniff" />
      </customHeaders>
    </httpProtocol>
  </system.webServer>
</configuration>

Referrer-Policy

When you click a link on a website, the calling URL is automatically transferred to the linked site. Unless this is necessary, you should disable it using the Referrer-Policy header:

context.Response.Headers.Add("Referrer-Policy", "no-referrer");

There are a lot of possible values for this header, like same-origin that will set the referrer as long as the user stays on the same website.

X-Permitted-Cross-Domain-Policies

You are probably not using Flash. Right? Right!!? If not, you can disable the possibility of Flash making cross-site requests using the X-Permitted-Cross-Domain-Policies header:

context.Response.Headers.Add("X-Permitted-Cross-Domain-Policies", "none");

Strict-Transport-Security

All pages should be served over HTTPS. To make sure that none of your content is still server over HTTP, set the Strict-Transport-Security header. The header can be set in custom middleware like in the previous examples. But ASP.NET Core already comes with middleware named HSTS (HTTP Strict Transport Security Protocol):

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        // ...
    }
    else
    {
        app.UseHsts();
    }
}

As shown in the code, it is recommended to use HSTS on production only, to avoid issues when developing locally. The Strict-Transport-Security header value can be customized through options:

public void ConfigureServices(IServiceCollection services)
{
    // ...
    
    services.AddHsts(options =>
    {
        options.IncludeSubDomains = true;
        options.MaxAge = TimeSpan.FromDays(365);
    });
}

This code will produce a header with subdomains included and a max-age of 1 year:

Strict-Transport-Security: max-age=31536000; includeSubDomains

X-Powered-By

Like ASP.NET, ASP.NET Core will return the X-Powered-By header. This happens when you host your website on IIS. This also means that you simply cannot remove the header in middleware, since this is out of hands for ASP.NET Core. web.config to the rescue:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <httpProtocol>
      <customHeaders>
        <remove name="X-Powered-By" />
      </customHeaders>
    </httpProtocol>
  </system.webServer>
</configuration>

Server

Like X-Powered-By, IIS kindly identify itself in the Server header. While hackers probably quickly find out anyway, you should still make it as hard as possible by removing the header. There's a dedicated security feature available in web.config to do that:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <security>
      <requestFiltering removeServerHeader="true" />
    </security>
  </system.webServer>
</configuration>

Permissions-Policy

The Permissions-Policy header (formerly known as Feature-Policy) tells the browser which platform features your website needs. Most web apps won't need to access the microphone or the vibrator functions available on mobile browsers. Why not be explicit about it to avoid imported scripts or framed pages to do things you don't expect:

context.Response.Headers.Add("Permissions-Policy", "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()");

Content-Security-Policy

I already wrote a rather long blog post about the Content-Security-Policy header. To avoid having to repeat myself, check out Content-Security-Policy in ASP.NET MVC for details. A content security policy can be easily added in ASP.NET Core by adding the header:

context.Response.Headers.Add("Content-Security-Policy", "default-src 'self'");

elmah.io: Error logging and Uptime Monitoring for your web apps

This blog post is brought to you by elmah.io. elmah.io is error logging, uptime monitoring, deployment tracking, and service heartbeats for your .NET and JavaScript applications. Stop relying on your users to notify you when something is wrong or dig through hundreds of megabytes of log files spread across servers. With elmah.io, we store all of your log messages, notify you through popular channels like email, Slack, and Microsoft Teams, and help you fix errors fast.

See how we can help you monitor your website for crashes Monitor your website