I think I have arrived at a solution. I'm not entirely happy with it, but it seems to do the job. This solution is in three parts:
- Apply the DisableCorsAttribute to the Blazor endpoint as recommended in the docs.
- Block cross-origin requests to the Blazor WebSockets endpoint.
- Apply authorization to the Blazor endpoint (strictly speaking this has nothing to do with cross origin requests, but I thought it worth a mention here).
1. DisableCorsAttribute
In Startup.ConfigureServices() add services.AddCors(); - I don't think I need to add a policy here, as I don't actually want to enable cors for any origins other than my own site.
In Startup.Configure() add app.UseCors(); somewhere after app.UseRouting(); but before app.UseEndpoints();
Use WithMetadata() to add DisableCorsAttribute to the Blazor endpoint metadata:
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub().WithMetadata(new DisableCorsAttribute());
endpoints.MapFallbackToPage("/_Host");
});
#2 Block cross-origin requests to the Blazor WebSockets endpoint CORS policies aren't applied to websockets (hence all the trouble). This solution seems a bit hacky, but it seems to work, I'm open to suggestions for improvements or better solutions. I have this at the top of my Startup.Configure() method so that cross-origin requests to the Blazor WebSockets endpoint will effectively just be ignored rather than have error codes returned to potential attackers.
app.Use(async (context, next) =>
{
if (context.Request.Path.Value == "/_blazor")
{
if (context.Request.Headers.TryGetValue("Origin", out var origin)
&& origin != $"https://{ context.Request.Host.Value }")
{
return; // Refuse to handle request
}
}
await next();
});
#3 Authorization I'm already using OIDC cookie based authentication in this application, so it seemed worth applying authorization to the Blazor endpoint so that only authenticated users can access it. It's worth noting that this in its self does not provide protection against cross-origin attacks that might somehow get an authenticated user to run code that connects to our Blazor endpoint, as the OIDC cookies are SameSite=None out of necessity. It seems like protection worth applying as an addition though.
This is achieved by adding .RequireAuthorization() to MapBlazorHub().
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub()
.RequireAuthorization()
.WithMetadata(new DisableCorsAttribute());
endpoints.MapFallbackToPage("/_Host");
});