The ASP.NET Core security headers guide
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.
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:
- Through middleware
- 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