/sotsera.blazor.server

Some Blazor Server extensions

Primary LanguageC#MIT LicenseMIT

Sotsera.Blazor.Server

sotsera.blazor.server

Some Blazor Server extensions

GitHub license Target GitHub last commit GitHub Actions Workflow Status NuGet NuGet Downloads

Security headers

A very simple middleware that adds headers to requests using the Response.OnStarting hook. In fact, it allows executing any code on an HttpContext at the start of a request, as it expects a type that implements the interface

public interface ISecurityHeadersPolicy
{
    void ApplyHeaders(HttpContext context, IWebHostEnvironment environment);
}

I needed a simple way to manage security headers on a Blazor Server site and, well, the name stuck.

Usage

Add the required services to the WebApplicationBuilder and, optionally, configure the only two settings available

using Sotsera.Blazor.Server.SecurityHeaders.Blazor;
using Sotsera.Blazor.Server.SecurityHeaders.Policies;
using Sotsera.Blazor.Server.SecurityHeaders.Policies.Permissions;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSecurityHeaders(c =>
{
    c.DisableKestrelServerHeader = true;
    c.AntiforgeryTokenPrefix = "SuperSecretToken";
});

Add the middleware to the pipeline specifying the default policy (example defined below)

var app = builder.Build();

app.UseSecurityHeaders(new DefaultPolicy());

Override the policy on any IEndpointConventionBuilder like, for example, on a group

// This endpoint will have the default policy
app.MapGet("with-default-headers", () => "default headers");

// Override the security headers for a specific or group of endpoints
var group = app.MapGroup("api")
    .RequireSecurityHeaders(new ApiPolicy());

// This endpoint will have the api policy
group.MapGet("with-api-headers", () => "api headers");

Disable the security headers for an IEndpointConventionBuilder

group.MapGet("without-headers", () => "without headers")
    .DisableSecurityHeaders();

Override the policy specifically for Blazor server with interactivity auto or web assembly. The library contains a SHA-256 provider for the importmap script added by the <ImportMap /> component which can be resolved by a policy in order to include the sha in the Content Security Policy (CSP).

app.MapRazorComponents<App>().AddInteractiveServerRenderMode()
    .RequireSecurityHeaders(new BlazorPolicy());

Example policies

// very basic policy
internal class DefaultPolicy : ISecurityHeadersPolicy
{
    public virtual void ApplyHeaders(HttpContext context, IWebHostEnvironment environment)
    {
        var headers = context.Response.Headers;
        headers.Remove("-- header name --");
        headers.XContentTypeOptions = "-- value --";
    }
}

// derived policy
internal class ApiPolicy : DefaultPolicy
{
    public override void ApplyHeaders(HttpContext context, IWebHostEnvironment environment)
    {
        base.ApplyHeaders(context, environment);
        context.Response.Headers.ContentSecurityPolicy = "-- value --";
    }
}

// Blazor specific policy with importmap's SHA-256 in the Csp and a simple Permission policy
internal class BlazorPolicy : DefaultPolicy
{
    public override void ApplyHeaders(HttpContext context, IWebHostEnvironment environment)
    {
        // retrieve the SHA-256 for the importmap script created by the <ImportMap /> component
        var provider = context.GetRequiredService<IBlazorImportMapDefinitionShaProvider>();
        var sha = provider.GetSha256(context);

        // append the sha to the allowed sources
        context.Response.Headers.ContentSecurityPolicy = $"script-src-elem {sha}";

        // disable the camera and geolocation usage
        context.Response.Headers["Permissions-Policy"] = new PermissionsPolicy
        {
            Camera = "()",
            Microphone = "()"
        };
    }
}

Thanks