Add Security Headers to Blazor WebAssembly

Microsoft Blazor wallpaper

In this new post, I like to explain how to add security headers to Blazor WebAssembly to follow the recommendations from OWASP. If you want to secure an ASP.NET Core Web Application, see my previous post “Add Security Header Our Website“.

The source code of this post is on GitHub. The demo website for this project is here.

The issue

When we create a new Blazor WebAssembly project, we assume the resulted website will be protected and with a minimum of security, also because Blazor is a new Microsoft technology. Unfortunately, it is not true. We discovered that there are a lot of open issues that we have to fix before deploying the project in production.

How to discover the major issues?

So, if you hire a penetration company they can suggest some expensive tools to use. Fortunately for us, there a couple of free tools we can use to test how website.

OWASP Zap

First, OWASP Zap is a tool build with Java that runs on your local machine and attaches your website to find vulnerability. This tool is open source and actively maintained by volunteers around the world.

Now, you can download OWASP Zap from the official website.

OWASP Zap website - Add Security Headers to Blazor WebAssembly
OWASP Zap website

Fron here, on the top right you see the button Download. From here, select the most appropriate version for your operating system and then run the installer. Probably, you need the Java Runtime.

OWASP Zap Download page - Add Security Headers to Blazor WebAssembly
OWASP Zap Download page

Observatory by Mozilla

Then, another free tool you can use from a website. This is the observatory by Mozilla. It is a good tool but unfortunately it is quite slow. You have a screenshot below.

Observatory Mozilla website - Add Security Headers to Blazor WebAssembly
Observatory Mozilla website

First, you have to type or paste the URL to scan and the click on button Scan Me. After that, the website is waiting for a free instance to start the scan. Sometimes, you have to wait for ages.

Security Headers

Security Headers analyse the HTTP response headers of other sites. Also, it adds a rating system to the results. The HTTP response headers that this site analyses provide huge levels of protection and it’s important that sites deploy them. Hopefully, by providing an easy mechanism to assess them, and further information on how to deploy missing headers, we can drive up the usage of security based headers across the web.

Security Headers website - Add Security Headers to Blazor WebAssembly
Security Headers website

What is OWASP?

The Open Web Application Security Project® (OWASP) is a nonprofit foundation that works to improve the security of software. Through community-led open-source software projects, hundreds of local chapters worldwide, tens of thousands of members, and leading educational and training conferences, the OWASP Foundation is the source for developers and technologists to secure the web.

  • Tools and Resources
  • Community and Networking
  • Education & Training

Create a new Blazor project

So, I assume we start from scratch a new project. Then, open Visual Studio and select new Blazor WebAssembly project. Type the name you want and the folder. Then, you arrive in the Additional Information page.

Because it is not possible to add middleware in the Blazor project, we have to use ASP.NET Core hosted for it also called Blazor Server hosting model (see Microsoft documentation).

Create a new Blazor project - Additional information page - Add Security Headers to Blazor WebAssembly
Create a new Blazor project – Additional information page

With the Blazor Server hosting model, the app is executed on the server from within an ASP.NET Core app. UI updates, event handling, and JavaScript calls are handled over a SignalR connection. The state on the server associated with each connected client is called a circuit. A circuit can tolerate temporary network interruptions and attempts by the client to reconnect to the server when the connection is lost.

In this solution, we have 3 project:

  • Client
  • Server
  • Shared
Solution structure
Solution structure

The Client project is the Blazor WebAssembly application. The Server is the ASP.NET Core web application that references the Client project. Then, the Shared project.

Now, I deploy the Server project to my live server and I want to run the scan test. I use the Mozilla Observatory and after few second the result is F and the score is 20/100. So, it is quite bad.

First scan of the Blazor WebAssembly project - Add Security Headers to Blazor WebAssembly
First scan of the Blazor WebAssembly project

Unfortunately, using Blazor we can’t have the perfect score. The maximum we can achieve will be a B+ that it is an improvement from the F at the beginning.

Why can’t we get the perfect score?

First, we start with Content Policy. This one is the one that you cannot remove from Blazor because there is some inline scripts that run for Blazor. So, the best we can do for that is -20.

Cookies: we don’t have any cookies on this project.

The Cross-origin Resource Sharing is set by your hosting like Azure is your deployment is on this cloud. Generally speaking, it should be ok. You might want to add to your code some settings to be more specific.

Now, we can add the HTTP Strict Transport Security setting but the default values are not enough. We can add app.UseHsts(); but we have to change the MaxAge at least. If your application is hosted in Azure, it doesn’t allow you to change the time. It is possible to fix it with Azure Front Door.

Then, Subresource Integrity won’t be use and this is used when you have scripts pulling from another part. For example, if you want to add Stripe, you have to add in this configuration Stripe and the put the actual ash of the soap resource that you are pulling to make sure that is the right resource and it asn’t been changed.

Finally, we have X-Content-Type-Options, X-Frame-Options and X-XSS-Protection that we are going to fix or at least improve the score.

Implementation

Now, the changes are mostly in the Program.cs file. If you host your web application on Azure, it is also better if you add a new web.config. All the changes are in the Server project!

Add a new web.config

So, this task is quite easy. In the Server project, add a new web.config and add this content

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

This configuration will remove the Powered By IIS or ASP.NET from the headers.

Change Program.cs

Now, the most important part. Here, the full source code of the Program.cs

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

builder.Services.AddHsts(options =>
{
    options.Preload = true;
    options.IncludeSubDomains = true;
    options.MaxAge = TimeSpan.FromDays(181);
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseWebAssemblyDebugging();
}
else
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. See https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.Use((context, next) =>
{
    context.Response.GetTypedHeaders().CacheControl =
        new Microsoft.Net.Http.Headers.CacheControlHeaderValue()
        {
            MustRevalidate = true,
            NoCache = true,
            NoStore = true,
        };

    string mainUrl = "https://observatory2.puresourcecode.com/";
#if DEBUG
    mainUrl = "https://localhost:7063";
#endif

    context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
    context.Response.Headers.Add("Content-Security-Policy",
        $"default-src 'self' {mainUrl} 'unsafe-inline' 'unsafe-eval'; " +
        $"script-src 'unsafe-inline' 'unsafe-eval' {mainUrl}; " +
        "connect-src 'self'; " +
        $"img-src 'self' {mainUrl}; " +
        $"style-src 'self' {mainUrl}; " +
        "base-uri 'self'; " +
        "form-action 'self'; " +
        "frame-ancestors 'none';");
    context.Response.Headers.Add("Referrer-Policy", "same-origin");
    context.Response.Headers.Add("Permissions-Policy", "geolocation=(), microphone=()");
    context.Response.Headers.Add("X-XSS-Protection", "1; mode=block");
    context.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN");
    context.Response.Headers.Add("SameSite", "Strict");

    return next.Invoke();
});

app.UseHttpsRedirection();
app.UseHsts();

app.UseBlazorFrameworkFiles();
app.UseStaticFiles();

app.UseRouting();

app.MapRazorPages();
app.MapControllers();
app.MapFallbackToFile("index.html");

app.Run();

So, I added to the boilerplain file only few new lines. Here the explanation:

  • 6-11: update the values for HTTP Strict Transport Security (HSTS) header set to a minimum of six months
  • 27: add the middleware. So, for each request the middleware will add this headers
  • 29: add cache control
  • 37: add a variable for the main URL. It changes if the application is in debug. So, we won’t have local addresses in production
  • 42-56: add the security headers
  • 61: force to redirect the requests to HTTPS
  • 62: use HSTS

This is all the code we need to improve the security in our Blazor WebAssembly application. Please let me know if I can improve more, leaving your comment below or in the forum.

Now, we have to check our hard work.

The result

After deploying the demo application on a live server, I run Mozilla Observatory and after few seconds… the result. It is what I expected from the beginning: a stunning B+ and a score of 80/100! As I said at the beginning, we can’t improve the Content Security Policy.

Mozilla Observatory website with the result
Mozilla Observatory website with the result

So, next stop Security Headers. Again, I type the address and here the result: A! This is not bad at all.

Security Headers with the result
Security Headers with the result

My last step is to use OWASP Zap to check if there are more vulnerabilities. After launching the application, I type in the URL to attack, the address where the application lives and the click on Attach. The result is in line with what I expect.

OWASP Zap with the result
OWASP Zap with the result

I’m not sure if the string, that is similar to a timestamp in UNIX, in the CSS is really a threat or not. The CSP is what we can’t fix for Blazor.

Random Blazor “Failed to find a valid digest in the ‘integrity’ attribute for resource”

This is an issue when loading a site built and published using Blazor. I get the following error message:

Failed to find a valid digest in the ‘integrity’ attribute for resource ‘https://MYWEBSITEURL.com/_framework/System.Private.CoreLib.dll’ with computed SHA-256 integrity ‘xV9SflNt5Ex5gP7OznQorlp2VkdJXkcAiopU+h5DRzY=’. The resource has been blocked.

I assume that the browser blocks the files from downloading because the hashes created when publishing do not match.

To fix this error, we have to change the index.html file in the Client project. It is necessary to replace the script related to service-worker.js with this code:

<script>navigator.serviceWorker.register('service-worker.js', { updateViaCache: 'none' });</script>

Leave a Reply

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