Improving application security in Blazor using HTTP headers – Part 2

This article shows how to improve the security of an ASP.NET Core Blazor application by adding security headers to all HTTP Razor Page responses (Blazor WASM hosted in a ASP.NET Core hosted backend). 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 authentication. The application is authenticated using OpenID Connect, the security headers are used to protected the session. The authentication is implemented in the Blazor application using the BFF pattern. The WASM client part is just a view of the server rendered trusted backend and cookies are used in the browser. All API calls are same domain only and protected with a cookie and same site.

Code: https://github.com/damienbod/AspNetCore6Experiments

Blogs in this series

The NetEscapades.AspNetCore.SecurityHeaders and the NetEscapades.AspNetCore.SecurityHeaders.TagHelpers Nuget packages are added to the csproj file of the web application. The tag helpers are added to use the nonce from the CSP in the Razor Pages.

<ItemGroup>
	<PackageReference 
		Include="NetEscapades.AspNetCore.SecurityHeaders" 
		Version="0.16.0" />
	<PackageReference 
		Include="NetEscapades.AspNetCore.SecurityHeaders.TagHelpers" 
		Version="0.16.0" />
</ItemGroup>

The Blazor definition is very similar to the ASP.NET Core Razor Page one. The main difference is that the CSP script policy is almost disabled due to the Blazor script requirements. We can at least force self on the content security policy header script-src definition.

The Blazor WASM logout link sends a HTTP Form POST request which is redirected to the OpenID Connect identity provider. The CSP needs to allow this redirect and the content secure policy form definition allows this.

public static HeaderPolicyCollection GetHeaderPolicyCollection(
     bool isDev, string identityProviderHost)
{
	var policy = new HeaderPolicyCollection()
		.AddFrameOptionsDeny()
		.AddXssProtectionBlock()
		.AddContentTypeOptionsNoSniff()
		.AddReferrerPolicyStrictOriginWhenCrossOrigin()
		.RemoveServerHeader()
		.AddCrossOriginOpenerPolicy(builder =>
		{
			builder.SameOrigin();
		})
		.AddCrossOriginEmbedderPolicy(builder =>
		{
			builder.RequireCorp();
		})
		.AddCrossOriginResourcePolicy(builder =>
		{
			builder.SameOrigin();
		})
		.AddContentSecurityPolicy(builder =>
		{
			builder.AddObjectSrc().None();
			builder.AddBlockAllMixedContent();
			builder.AddImgSrc().Self().From("data:");
			builder.AddFormAction().Self().From(identityProviderHost);
			builder.AddFontSrc().Self();
			builder.AddStyleSrc().Self().UnsafeInline();
			builder.AddBaseUri().Self();
			builder.AddFrameAncestors().None();

			// due to Blazor
			builder.AddScriptSrc().Self().UnsafeInline().UnsafeEval();
		})
		.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();
		});

	if (!isDev)
	{
		// maxage = one year in seconds
		policy.AddStrictTransportSecurityMaxAgeIncludeSubDomains(maxAgeInSeconds: 60 * 60 * 24 * 365);
	}

	return policy;
}

Blazor adds the following script to the WASM host file. This means the CSP for scripts cannot be implemented in a good way.

<script>
var Module; window.__wasmmodulecallback__(); delete window.__wasmmodulecallback__;
</script>


script-src (from CSP evaluator)

‘self’ can be problematic if you host JSONP, Angular or user uploaded files.
‘unsafe-inline’ allows the execution of unsafe in-page scripts and event handlers.
‘unsafe-eval’ allows the execution of code injected into DOM APIs such as eval().

The aspnetcore-browser-refresh.js is also added for hot reload. This also prevents a strong CSP script definition in development. This could be fixed with a dev check in the policy definition. There is no point fixing this, until the wasmmodulecallback script bug is fixed.

I am following the ASP.NET Core issue and hope this can be improved for Blazor.

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.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
	if (env.IsDevelopment())
	{
		app.UseDeveloperExceptionPage();
	}
	else
	{
		app.UseExceptionHandler("/Error");
	}

	app.UseSecurityHeaders(
		SecurityHeadersDefinitions
			.GetHeaderPolicyCollection(env.IsDevelopment(),
                    Configuration["AzureAd:Instance"]));

The server header can be removed in the program class file of the Blazor server project 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>();
		});

When we scan the https://securityheaders.com/ you can view the results. You might need to disable the authentication to check this, or provide a public view.

The content security policy has a warning due to the script definition which is required for Blazor.

The https://csp-evaluator.withgoogle.com/ also displays a high severity finding due the the CSP script definition.

Notes:

If the application is fully protected without any public views, the follow redirects checkbox on the security headers needs to be disabled as then you only get the results of the identity provider used to authenticate.

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.

Maybe until the CSP script is fixed for Blazor, you probably should avoid using Blazor for high security applications and use ASP.NET Core Razor Page applications instead.

If you use Blazor together with tokens in Azure AD or Azure B2C and this CSP script bug, you leave yourself open to having your tokens stolen. I would recommend using server authentication with Azure which removes the tokens from the browser and also solves the Azure SPA logout problem. Azure AD, Azure B2C do not support the revocation endpoint or introspection, so it is impossible to invalidate your tokens on a logout. It does not help if the IT admin, Azure monitoring can invalidate tokens using CAE.

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

3 comments

  1. […] Improving application security in Blazor using HTTP headers – Part 2 […]

  2. […] Improving application security in Blazor using HTTP headers – Part 2 (Damien Bowden) […]

  3. […] New Videos Covering Vue 3.2 (Shawn Wildermuth) 5 Ways Azure Cognitive Services Scale (Amy Boyd) Improving application security in Blazor using HTTP headers – Part 2 (Damien Bowden) Easy Steps to Synchronize JIRA Calendar Tasks With the Blazor Scheduler (Mahesh […]

Leave a Reply to Improving application security in ASP.NET Core Razor Pages using HTTP headers – Part 1 | Software Engineering Cancel 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 )

Google photo

You are commenting using your Google 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: