Use YARP to host client and API server on a single origin to avoid CORS
Tags: dotnet, aspnet, blazor
Published: 2021-01-25 21:11:12

---
title: Use YARP to host client and API server on a single origin to avoid CORS
published: true
date: 2021-01-24 00:00:00 UTC
tags: dotnet, aspnet, blazor
canonical_url: https://swimburger.net/blog/dotnet/use-yarp-to-host-client-and-api-server-on-a-single-origin
cover_image: https://swimburger.net/media/vb5n3wqa/dotnet-yarp-social.png?anchor=center&mode=crop&width=1000&height=420
---

Applications are often split up into a client and a server. The client renders the UI and communicates to the server through an API. But when the client and server are split up into separate projects and hosted at different origins ( **origin == scheme + host + port** ), it can be a hassle for the client to communicate with the server.

On the web specifically, communicating between different origins can be painful. By default, clients are not allowed to make ajax requests to a different origin. The server has to use **Cross Origin Resource Sharing (CORS)** headers to allow cross origin ajax requests.

Another way to resolve CORS challenges, is to circumvent it entirely by having the client and server hosted on the same origin. If your technology stack allows for it, you can merge the client and server into a single project and host them on a single origin. If that's not possible, you can **use a reverse proxy to consolidate the client origin and the server origin under the proxy's origin**.

![A diagram: web browser points to reverse proxy server. Reverse proxy server points to two web servers. The first web server is an API server. The second server is a frontend website server. The diagram visualizes how the HTTP requests from a web browser are sent to the reverse proxy. If the requested path starts with "/API/", the proxy forwards the request to the API server. Otherwise the request is sent to the Client Web Server which serves the frontend of your application.](https://dev-to-uploads.s3.amazonaws.com/i/rvn4yq7qhmqtxjt82yck.png)
 
The diagram above visualizes how the HTTP requests from a web browser are sent to the reverse proxy. If the requested path starts with '_/API/_', the proxy forwards the request to the API server. Otherwise the request is sent to the Client Web Server which serves the frontend of your application.

You can pick any option for your frontend client, backend server, and reverse proxy server. It does not matter because they all understand the same language: **HTTP**. In this walkthrough, you'll learn how to communicate between a **Blazor WebAssembly (WASM)** client and a **ASP.NET Web API**  **without CORS**. The client and server will be hosted on a single origin by putting Microsoft's new reverse proxy: **YARP** (which stands for "YARP: A Reverse Proxy")
![A diagram: web browser points to the YARP reverse proxy. YARP server points to two web servers. The first web server is an ASP.NET Core WebAPI server. The second server is a Blazor WebAssembly server. The diagram visualizes how the HTTP requests from a web browser are sent to YARP. If the requested path starts with "/API/", the proxy forwards the request to the ASP.NET Core WebAPI server. Otherwise the request is sent to the Client Web Server which serves the Blazor WebAssembly application.](https://dev-to-uploads.s3.amazonaws.com/i/ovmkqcje18wzsqg4pui8.png)
 
 
But why YARP instead of Apache or Nginx? YARP is built on top of ASP.NET Core which gives you some benefits:

- You can configure YARP with any supported external configuration like JSON, environment variables, and many more. 
- You can also use .NET code to configure and extend your reverse proxy.
- You can store this piece of infrastructure in source control like any other ASP.NET Core project.
- You can increase consistency between your local environment and server environments.
- It's cross platform, you can develop locally on your OS of choice but deploy it to your server OS of choice. For example, you can develop on Windows but use Linux machines for production.

## Prerequisites

To follow along you need the following tech:

- .NET Core 3.1 or up ([installation instructions](https://dotnet.microsoft.com/download))
- Windows PowerShell or PowerShell Core ([installation instructions](https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell))
- OS which supports the above tech such as Windows, Mac, or Linux

You can find all the source code for the end result on [GitHub](https://github.com/Swimburger/YarpClientServerSingleOrigin).

## Create the Blazor WASM client

Before creating the client, create a folder where all projects will reside using the following commands:

```powershell
mkdir YarpClientServerSingleOrigin
cd YarpClientServerSingleOrigin
```

Create a solution file using the dotnet command line interface (CLI):

```powershell
dotnet new sln
```

Create the Blazor WASM client:

```powershell
dotnet new blazorwasm -n Client -o Client
```

**-n** : specifies the name of the project

**-o** : specifies the location where the project will be created

Using the command above, you created a Blazor WASM project named 'Client' which is also used as the default namespace. This project is created in a subfolder also named 'Client'.

Add the client project to the solution file:

```powershell
dotnet sln add Client
```

When you take a look at the FetchData component at _Client\Pages\FetchData.razor_, you can see the client is requesting data about the weather from a static JSON file:

```csharp
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
```

Update this line of code to fetch the data from `api/WeatherForecast` instead:

```csharp
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("api/WeatherForecast");

```

This API endpoint does not exist yet, but you will create this soon.

## Create the ASP.NET Core Web API server

Create the Web API project using the dotnet CLI:

```powershell
dotnet new webapi -n Server -o Server
```

Using the command above, you created a Web API project named 'Server' which is also used as the default namespace. This project is created in a subfolder also named 'Server'.

Add the server project to the solution file:

```powershell
dotnet sln add Server
```

When you try to run the client and server at the same time, an error will be shown because they use the same port by default. The ports used by these projects are specified at '_Properties\launchSettings.json_'. To prevent port collision, replace ' **https://localhost:5001;http://localhost:5000**' with ' **https://localhost:5003;http://localhost:5002**' at '_Server\Properties\launchSettings.json_'. Or run these PowerShell commands:

```powershell
$launchSettingsContents = Get-Content -path ./Server/Properties/launchSettings.json -Raw
$launchSettingsContents = $launchSettingsContents -replace 'https://localhost:5001;http://localhost:5000','https://localhost:5003;http://localhost:5002'
Set-Content -Value $launchSettingsContents -Path ./Server/Properties/launchSettings.json
```

There is a single controller that comes out of the box with the WebAPI template: WeatherForecastController.cs  
This controller generates random weather forecasts and luckily for you matches the data model used in the Blazor WASM client. The WeatherForecast API can be requested at '_/WeatherForecast_', but the client will request it at '_/api/WeatherForecast_'. The reverse proxy you will create next will suffix the API with '_/api_'.

## Create the YARP proxy

Unlike some other reverse proxies, you cannot simply install it on your server and configure it. Instead, you have to create an empty ASP.NET Core Web project, add the YARP NuGet package, and configure YARP using configuration or code. Then you need to build, publish, and install it onto your server like any other ASP.NET Core application. This walkthrough will go through building and running this locally for development only.

Create an empty ASP.NET Core Web project using the dotnet CLI:

```powershell
dotnet new web -n Proxy -o Proxy
```

Using the command above, you created an empty web project named 'Proxy' which is also used as the default namespace. This project is created in a subfolder also named 'Proxy'. In this case, Proxy is short for Reverse Proxy and not Forward Proxy.

Add the proxy project to the solution file:

```powershell
dotnet sln add Proxy
```

To prevent port collision, you need to update the ports specified in '_launchSettings.json_' just like the Web API project. Replace ' **https://localhost:5001;http://localhost:5000**' with ' **https://localhost:5005;http://localhost:5004**' at '_Proxy\Properties\launchSettings.json_'. Or run these PowerShell commands:
```powershell
$launchSettingsContents = Get-Content -path ./Proxy/Properties/launchSettings.json -Raw $launchSettingsContents = $launchSettingsContents -replace 'https://localhost:5001;http://localhost:5000','https://localhost:5005;http://localhost:5004' Set-Content -Value $launchSettingsContents -Path ./Proxy/Properties/launchSettings.json
```

Add the prelease [NuGet package of YARP](https://www.nuget.org/packages/Microsoft.ReverseProxy/) to the proxy project:

```powershell
dotnet add Proxy package Microsoft.ReverseProxy --prerelease
```

The package 'Microsoft.ReverseProxy' is at version '1.0.0-preview.8.21065.1' at the time of writing this. If any of the upcoming functionality does not work as expected with newer versions of YARP, make sure to check out [YARP's documentation](https://microsoft.github.io/reverse-proxy/index.html).

Update the '_Startup.cs_' file to enable YARP's functionality:

```csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace Proxy
{
    public class Startup
    {
        public IConfiguration Configuration { get; }

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddReverseProxy()
                .LoadFromConfig(Configuration.GetSection("ReverseProxy"));
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapReverseProxy();
            });
        }
    }
}

```

You can use any external configuration like 'appsettings.json' or environment variables to configure YARP. You can also configure the reverse proxy dynamically using code.

Update the '_Proxy\appsettings.Development.json_' file:

```json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "ReverseProxy": {
    "Clusters": {
      "Client": {
        "Destinations": {
          "Client1": {
            "Address": "https://localhost:5001"
          }
        }
      },
      "Server": {
        "Destinations": {
          "Server1": {
            "Address": "https://localhost:5005"
          }
        }
      }
    },
    "Routes": [
      {
        "RouteId": "ServerRoute",
        "ClusterId": "Server",
        "Match": {
          "Path": "/api/{\*\*catch-all}"
        },
        "Transforms": [
          {
            "PathRemovePrefix": "/api"
          }
        ]
      },
      {
        "RouteId": "ServerSwaggerRoute",
        "ClusterId": "Server",
        "Match": {
          "Path": "/swagger/{\*\*catch-all}"
        }
      },
      {
        "RouteId": "ClientRoute",
        "ClusterId": "Client",
        "Match": {
          "Path": "{\*\*catch-all}"
        }
      }
    ]
  }
}

```

The above JSON configures YARP as follows:

- 2 clusters are defined

1. A cluster named " **Client**"

- Client cluster only has one destination named " **Client1**" which points to the local development URL for the Blazor WASM client.
- A cluster named " **Server**"

- Server cluster only has one destination named " **Server1**" which points to the local development URL for the Web API server.
- 3 routes are defined

1. A route named " **ServerRoute**" which forwards HTTP requests to the "Server" cluster any time the requested path starts with '_/api/_'. Before the HTTP request is forwarded to the Web API, the '_/api_' prefix is removed by the "PathRemovePrefix" transform.
2. A route named " **ServerSwaggerRoute**" which fixes the Swagger UI for the Web API. The Swagger UI isn't fully aware it is now hosted under the '_/api_' suffix and this extra route fixes some of those HTTP requests made to the wrong path.
3. A route name " **ClientRoute**" which forwards HTTP requests to the "Client" cluster for all remaining requests.

In this case, there is only one destination per cluster, but YARP can handle multiple destinations and the reverse proxy will load balance the HTTP requests.

## Testing the result

You need to open three different terminals. One for every project. Run the following commands:

- Terminal 1: `dotnet run -p Client`
- Terminal 2: `dotnet run -p Server`
- Terminal 3: `dotnet run -p Proxy`

Open the browser and navigate to the Proxy which should be accessible at '_https://localhost:5005_'.

The Blazor client should be showing up. Click on the "Fetch Data" navigation link.

The weather forecast data should populate just as you expect it from an out of the box Blazor WASM project. But every time you reload, the data should be different because it is being generated by the Web API project.

You can write a script to bring up and down all three projects instead of manually running them in three different terminals. OR you could use [Microsoft's experimental project Tye](https://github.com/dotnet/tye) to **tie** everything together.

Install Tye as a .NET global tool:

```powershell
dotnet tool install -g Microsoft.Tye --version "0.5.0-alpha.20555.1"
```

You can find the most recent version of [Tye at Nuget](https://www.nuget.org/packages/Microsoft.Tye).

Initialize tye using this command:

```powershell
tye init
```

This will create a configuration YAML file '_tye.yaml_'. Update the YAML file to match the content below:

```yaml
name: yarpclientserversingleorigin
services:
- name: client
  project: Client/Client.csproj
  bindings:
  - port: 5001
    protocol: https
- name: server
  project: Server/Server.csproj
  bindings:
  - port: 5003
    protocol: https
- name: proxy
  project: Proxy/Proxy.csproj
```

Tye will automatically use random ports for projects if you do not specify them. The Proxy project expects the client and server project to be run on port 5001 and 5003. Hence you need to explicitly configure which port and protocol these projects use in the YAML file.

Run this command to bring up all projects at once with tye:

```powershell
tye run
```

You can find the proxy URL in the output, or you can navigate to tye's dashboard at _localhost:8000_

![Screenshot of the project Tye dashboard](https://swimburger.net/media/i5xd4ecg/tye-dashboard.png)

In addition to finding the URL for each service, you can also watch the log streams of each service.

# Summary

API's need to provide CORS headers to explicitly allow ajax requests from web clients hosted on a different origin. Reverse proxies can merge the API origin and client origin onto a single origin. Using Microsoft's new reverse proxy "YARP", you configured the proxy to forward requests to '_/api_' to the Web API, and all other requests to the Blazor WASM client.  
Using project Tye, you tied all projects together so you can easily run all projects at once instead of starting them in 3 different terminals.

You can learn more about YARP at [YARP's Documentation](https://microsoft.github.io/reverse-proxy/index.html) and you can learn more about project Tye on [its GitHub repository](https://github.com/dotnet/tye).