Improving application security in an ASP.NET Core API using HTTP headers – Part 3

This article shows how to improve the security of an ASP.NET Core Web API application by adding security headers to all HTTP API responses. The security headers are added using the NetEscapades.AspNetCore.SecurityHeaders Nuget package from Andrew Lock. The headers are used to protect the session, not for authorization. The application uses Microsoft.Identity.Web to authorize the API requests. The security headers are used to protected the session. Swagger is used in development and the CSP needs to be weakened to allow swagger to work during development. A strict CSP definition is used for the deployed environment.

Code: https://github.com/damienbod/AzureAD-Auth-MyUI-with-MyAPI

Blogs in this series

History

2023-03-11 fix: applied security headers to all requests

The NetEscapades.AspNetCore.SecurityHeaders Nuget package is added to the csproj file of the web applications. The Swagger Open API packages are added as well as the Microsoft.Identity.Web to protect the API using OAuth.

<ItemGroup>
    <PackageReference 
       Include="Microsoft.Identity.Web" Version="2.5.0" />
    <PackageReference 
       Include="IdentityModel.AspNetCore" Version="4.3.0" />
    <PackageReference 
       Include="NetEscapades.AspNetCore.SecurityHeaders" Version="0.18.0" />
    <PackageReference 
       Include="Swashbuckle.AspNetCore" Version="6.5.0" />
    <PackageReference 
       Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0" />
</ItemGroup>

The security header definitions are added using the HeaderPolicyCollection class. I added this to a separate class to keep the Startup class small where the middleware is added. I passed a boolean parameter into the method which is used to add or remove the HSTS header and create a CSP policy depending on the environment.

public static HeaderPolicyCollection GetHeaderPolicyCollection(bool isDev)
{
	var policy = new HeaderPolicyCollection()
		.AddFrameOptionsDeny()
		.AddXssProtectionBlock()
		.AddContentTypeOptionsNoSniff()
		.AddReferrerPolicyStrictOriginWhenCrossOrigin()
		.AddCrossOriginOpenerPolicy(builder => builder.SameOrigin())
		.AddCrossOriginEmbedderPolicy(builder => builder.RequireCorp())
		.AddCrossOriginResourcePolicy(builder => builder.SameOrigin())
		.RemoveServerHeader()
		.AddPermissionsPolicy(builder =>
		{
			builder.AddAccelerometer().None();
			builder.AddAutoplay().None();
			builder.AddCamera().None();
			builder.AddEncryptedMedia().None();
			builder.AddFullscreen().All();
			builder.AddGeolocation().None();
			builder.AddGyroscope().None();
			builder.AddMagnetometer().None();
			builder.AddMicrophone().None();
			builder.AddMidi().None();
			builder.AddPayment().None();
			builder.AddPictureInPicture().None();
			builder.AddSyncXHR().None();
			builder.AddUsb().None();
		});

	AddCspHstsDefinitions(isDev, policy);

	policy.ApplyDocumentHeadersToAllResponses();

	return policy;
}

The AddCspHstsDefinitions defines different policies using the parameter. In development, the HSTS header is not added to the headers and a weak CSP is used so that the Swagger UI will work. This UI uses unsafe inline Javascript and needs to be allowed in development. I remove swagger from all non dev deployments due to this and force a strong CSP definition then.

private static void AddCspHstsDefinitions(bool isDev, HeaderPolicyCollection policy)
{
	if (!isDev)
	{
		policy.AddContentSecurityPolicy(builder =>
		{
			builder.AddObjectSrc().None();
			builder.AddBlockAllMixedContent();
			builder.AddImgSrc().None();
			builder.AddFormAction().None();
			builder.AddFontSrc().None();
			builder.AddStyleSrc().None();
			builder.AddScriptSrc().None();
			builder.AddBaseUri().Self();
			builder.AddFrameAncestors().None();
			builder.AddCustomDirective("require-trusted-types-for", "'script'");
		});
		// maxage = one year in seconds
		policy.AddStrictTransportSecurityMaxAgeIncludeSubDomains
          (maxAgeInSeconds: 60 * 60 * 24 * 365);
	}
	else
	{
		// allow swagger UI for dev
		policy.AddContentSecurityPolicy(builder =>
		{
			builder.AddObjectSrc().None();
			builder.AddBlockAllMixedContent();
			builder.AddImgSrc().Self().From("data:");
			builder.AddFormAction().Self();
			builder.AddFontSrc().Self();
			builder.AddStyleSrc().Self().UnsafeInline();
			builder.AddScriptSrc().Self().UnsafeInline(); //.WithNonce();
			builder.AddBaseUri().Self();
			builder.AddFrameAncestors().None();
		});
	}
}

In the Startup class, the UseSecurityHeaders method is used to apply the HTTP headers policy and add the middleware to the application. The env.IsDevelopment() is used to add or not to add the HSTS header. The default HSTS middleware from the ASP.NET Core templates was removed from the Configure method as this is not required. The UseSecurityHeaders is added before the swagger middleware so that the security headers are deployment to all environments.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
	app.UseSecurityHeaders(
		SecurityHeadersDefinitions.GetHeaderPolicyCollection(env.IsDevelopment()));

	if (env.IsDevelopment())
	{
		app.UseDeveloperExceptionPage();

		app.UseSwagger();
		app.UseSwaggerUI(c =>
		{
			c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1");
		});
	}

The server header can be removed in the program class if using Kestrel. If using IIS, you probably need to use the web.config to remove this.

public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder
                        .ConfigureKestrel(options => options.AddServerHeader = false)
                        .UseStartup<Startup>();
                });

Running the application using a non development environment, the securtiyheaders.com check returns good results. Everything is closed as this is an API with no UI.

And the https://csp-evaluator.withgoogle.com/ check returns a very possible evaluation of the headers.

If a swagger UI is required, the API application can be run in the development environment. This could also be deployed if required, but in a production deployment, you probably don’t need this.

To support the swagger UI, a weakened CSP is used and the https://csp-evaluator.withgoogle.com/ check returns a more negative result.

Notes:

I block all traffic, if possible, which is not from my domain including sub domains. If implementing enterprise applications, I would always do this. If implementing public facing applications with high traffic volumes or need extra fast response times, or need to reduce the costs of hosting, then CDNs would need to be used, allowed and so on. Try to block all first and open up as required and maybe you can avoid some nasty surprises from all the Javascript, CSS frameworks used.

Links

https://securityheaders.com/

https://csp-evaluator.withgoogle.com/

Security by Default Chrome developers

A Simple Guide to COOP, COEP, CORP, and CORS

https://github.com/andrewlock/NetEscapades.AspNetCore.SecurityHeaders

https://github.com/dotnet/aspnetcore/issues/34428

https://w3c.github.io/webappsec-trusted-types/dist/spec/

https://web.dev/trusted-types/

https://developer.mozilla.org/en-US/docs/Web/HTTP/Cross-Origin_Resource_Policy_(CORP)

https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies

https://docs.google.com/document/d/1zDlfvfTJ_9e8Jdc8ehuV4zMEu9ySMCiTGMS9y0GU92k/edit

https://scotthelme.co.uk/coop-and-coep/

https://github.com/OWASP/ASVS

10 comments

  1. […] Improving application security in an ASP.NET Core API using HTTP headers – Part 3 (Damien Bowden) […]

  2. […] 2021) (Azure SDK Team) Support for fetching nested paths in OData Web API (Clement Habinshuti) Improving application security in an ASP.NET Core API using HTTP headers – Part 3 (Damien Bowden) Setting up a CI/CD Pipeline for your JavaScript Project (Eugene Panchenko) […]

  3. […] Improving application security in an ASP.NET Core API using HTTP headers – Part 3 – Damien Bowden […]

  4. I thought most of these HTTP headers only really applied to HTML pages. If you aren’t shipping the Swagger UI, do you strictly need them? If you do ship the Swagger UI, I thought you’d only need the headers returned from the HTML page response itself.

    1. Hi Muhammad, yes and no, if you don’t need this, then close it down, I see most API domains hosting static files or html which sometimes gets added later in the project after the initial release and after the security testing is complete. This way you protect the API host per default and force an active decision on security when adding UIs, static content. Why leave this open if you don’t need it?

      Greetings Damien

  5. Is a way to ensure the headers only get added to the Swagger UI page?

    Thanks for the post, very interesting.

    1. Thanks, this would be possible if you re-order the middleware in the configure method. My question is, why not add them the all requests? Greetings Damien

      1. Performance I guess because they don’t apply to JSON API endpoints.

  6. Admittedly, it won’t make much difference.

  7. […] Improving application security in an ASP.NET Core API using HTTP headers – Part 3 […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: