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

Improving application security in ASP.NET Core Razor Pages using HTTP headers – Part 1
Improving application security in Blazor using HTTP headers – Part 2
Improving application security in an ASP.NET Core API using HTTP headers – Part 3

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=”1.15.2″ />
<PackageReference
Include=”IdentityModel.AspNetCore” Version=”3.0.0″ />
<PackageReference
Include=”NetEscapades.AspNetCore.SecurityHeaders” Version=”0.16.0″ />
<PackageReference
Include=”Swashbuckle.AspNetCore” Version=”6.1.4″ />
<PackageReference
Include=”Swashbuckle.AspNetCore.Annotations” Version=”6.1.4″ />
</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()
.RemoveServerHeader()
.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);

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

Leave a Reply

Your email address will not be published. Required fields are marked *