Friday, February 3, 2023

localhost has been blocked by CORS policy

For many years I could not debug a Web API service and Blazor app on localhost. I would debug-run the service in one instance of Visual Studio 2022 and the Blazor app in another instance. The first client call to the service would return:

Access to fetch at 'http://localhost:5086/endpoint' from origin 'http://localhost:56709' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

At first this was only an inconvenience and I spent a couple of hours annually trying to overcome the problem. Hundreds of web search results produced a confusing jumble of suggestions, some ridiculously complex, some for the wrong platform, some absurd, and some seemingly sensible ones that did not work! In early 2023 the inability to debug on my desktop over localhost became a serious impediment and I swore to solve it. After approximately 3 solid hours of research and experiments I found the answer.

In the Program.cs code of the .NET 6 Web Api project you insert an AddCors statement as soon as the builder is created.

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
  options.AddDefaultPolicy(policy =>
  {
    policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin();
  });
});

Further down, as soon as the app is created, follow it with a UseCors statement.

var app = builder.Build();
app.UseCors();

I'm sure I've seen that fix code over the years, but it never worked. Perhaps I was on an older Framework, or maybe I had the code in the wrong sequence, or maybe any other number of subtle mistakes could have sabotaged my experiments. The most infuriating aspect of the problem is that the client error message tells you that the 'Access-Control-Allow-Origin' response header is missing, which is true, but it's not the core of the problem. I think I wasted hours in futile efforts to add the header.

The breakthrough clue about what was really causing the problem was revealed by Fiddler, it showed that the first web service call was an OPTIONS request to the service endpoint. The response was 405 Method Not Allowed, which helped steer me to the fix code listed above. It wasn't clear sailing, because I forgot to add the UseCors statement and I wasted half an hour wondering why the AddCors was having no effect.

You may ask why I didn't use Fiddler years ago … I did, but it would never show me the localhost traffic coming through Visual Studio. Now, during my latest efforts, for some unexplained reason I am seeing all the traffic and the problem was revealed.

I'm quite shocked to see that every client call to the web service is silently an OPTIONS, which is then followed by the real request verb. Firstly I worry about the overhead, and then I worry about secret requests being made without my knowledge. I'll have to research when and why this happens, and I'll append a note if I find something useful.

UPDATE May 2023

Run a search for words like "OPTIONS CORS PREFLIGHT" and you will find explanations of the mechanism I complained about. The overhead of preflight requests is not as bad as it looks. There are optimisations and the concept of simple requests that make CORS less onerous that it seems. CORS is however a damn curse on developer testing.