How to Build a CORS Proxy for Client-side Blazor

Blazor is a godsend for C# developers who want to build web applications. I’ve found building web apps in Blazor quick and easy, but there are a couple of fish-hooks that could catch you out. CORS is one.

I had a server-side app that was working fine. Turning a server-side Blazor app into a client-side Blazor app is mostly painless, but this time nothing worked.

The app was making a call to an API but from the client-side version of the app, all the calls failed. They’d been blocked by the browser’s CORS policy.

The answer was to build a CORS proxy. Here’s what I did.

The error dumped to the console said:

Access to fetch at ‘https://[base address]/auth/token‘ from origin ‘https://localhost:44327‘ 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.

Blazor goes to a lot of effort to make sure that the same code can run on the server and the client. But there are some things that it can’t hide. Client-side Blazor is running in a .NET runtime that’s compiled to web assembly. Even though you haven’t written a line of JavaScript, it runs inside the browser’s JavaScript sandbox.

The code that I’d written was in C#, using HttpClient. Under the covers, it was using the browser’s Fetch API, so browser security rules apply.

Web browsers enforce a same-origin policy that restricts how a site can interact with resources from another site. Usually things like images, CSS, and other embedded resources are allowed but a lot of JavaScript requests are not. The reason is to prevent malicious JavaScript from getting up to mischief.

Say you’d logged into your internet banking and on another tab a dodgy ad had loaded some malicious JavaScript. That script could try to make a transaction on the banking site, which could succeed because your authentication cookies are still available. The browser stops the API call because the dodgy ad script doesn’t have the same origin (i.e. protocol, host, and port) as the bank’s API.

So far, so good, but the same-origin policy prevents legitimate calls as well.

The answer is CORS (cross-origin request sharing).

With CORS, the browser can make a “pre-flight” request to the server to check whether the request should be allowed. The server will provide response headers that indicate whether the request can go ahead or not. Depending on what these headers say, the browser will either make the request or fail.

Again, so far, so good, but you may want to call an API that doesn’t return the right headers. My app was talking to an API that didn’t cooperate and that I couldn’t change. To get it to work, I needed to put a proxy in between the browser and the target API. The proxy should respond to the preflight request so that the browser sends the main request. It should then pass the request on to the real target, and pass back the response.

There are free CORS proxies you could use but these proxies have access to all your traffic, even if you use HTTPS, so don’t use them for anything that has usernames and passwords or other information you want to keep secret.

Creating a CORS proxy in .NET is quite straightforward. The ASP.NET Core middleware has some pre-built CORS components we can use for the client application-facing side of the proxy.

Create a new API project. In the project’s Startup class, in the ConfigureServices method, use the AddCors extension method to add a policy to the CORS options.

services.AddCors(options =>
{
    options.AddPolicy(
        "Open",
        builder => builder.AllowAnyOrigin().AllowAnyHeader());
});

The CorsPolicyBuilder object has a set of chainable methods that set various rules for the allowed origins, headers, and methods. Here I’ve just said that I’ll accept any origin and any header. Note that this is for demo purposes only: accepting any origin isn’t really safe because it allows any site to connect to the proxy.

In the Configure method, call UseCors with the name of the policy you’ve just defined.

app.UseRouting();
app.UseCors("Open");
app.UseAuthorization();

Middleware order is important. The CORS setup must come after routing and before authorisation. And that’s it for allowing the client to connect to the proxy.

Next, we need to take the requests that we’ve allowed from the client and pass them on to the target API. Again, the pieces have already been built and we just need to put them together.

From NuGet, install

The HTTP Abstractions assembly adds the ability to branch the middleware pipeline using the MapWhen function and the proxy assembly passes the request on to the target.

Add a new mapping rule to Startup’s Configure method

app.MapWhen(
    context => context.Request.Path.StartsWithSegments("/api"),
    builder => builder.RunProxy(new ProxyOptions
    {
        Scheme = "https",
        Host = "example.com",
        Port = "80",
    })
);

The predicate can examine the request and decide whether to take the branch. Any request that comes in and meets the condition will be redirected according to the proxy options.

The proxy isn’t bound by the JavaScript sandbox rules and can make the request directly, without the same-origin policy or the pre-flight request. When it gets a response, it passes it back to the orignal caller.

If you need to call multiple services, you can add multiple mapping rules and pass all your requests through the same proxy.

The entire thing is done in middleware – the project has no controllers or actions. With just a few lines of code, one of the obstacles to running everything in the browser can be avoided.

One thought on “How to Build a CORS Proxy for Client-side Blazor

  1. Hi Bernard,
    Could you please give me the GIT code path for this example. I did the same but still getting the same CORS error.

    Thanks a lot in advance.
    Rahul Gupta

    Like

Leave a 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