Back to top
image_not_found

Using YARP with multiple domains

I’m currently going through the process of upgrading one of my ASP.NET web applications from .NET Framework (4.8) to .NET 6 (Core, baby!). As anyone who has been through this or attempted to start this process would know, as soon as you’re dealing with a website that is not “simple”, it can be a very slow and arduous process. Things like databases and authentication have a nasty way of getting in the way.

For this project, we cannot afford to put a code freeze in place for a month or two while we work on the upgrade. Also, right now I’m the sole developer which makes it a little easier to manage, but that will stop being the case soon, and having more developers working on the existing code will also make a gradual migration trickier.

So I was ecstatic to learn about the release of the new Incremental ASP.NET Migration Tooling. This basically gets us started with an empty.NET Core project and then helps us to gradually migrate parts of the existing framework app (e.g. by Controller or View) to the .NET Core project. It also goes hand in hand with the System.Web Adapters that will allow you to use any existing code that references System.Web’s HttpContext in your class libraries, if you haven’t already taken that intermediate step of getting your class libraries compiling in .NET Standard 2.

Using this setup, we can do the migration to .NET Core using the Stranger Fig Pattern, where we replace the old project bit-by-bit.

In our situation, we have extra complications such as having ASP.NET Identity in use for our authentication. I was able to get shared authentication working across the Framework and Core sites by using the example code for using a Shared Authentication Cookie. If you are struggling to get that working, also check out this helpful thread on Github where a few people nutted out more issues with this.

Configuring YARP for multiple domains

I’ve naturally hit some snags along the way, and the most recent one was to do with handling multiple domains on the one site.

In this project that I’m migrating, we offer a “white-label domain” (WLD) solution, which means that clients could be running the platform under their own domain so THEIR customers can see a lovely branded version which has basically no reference to the source platform. The way we have this setup is that we simply have all those extra custom domains set up on our base site. If they are logged in on one of these WLDs, they naturally are not logged in on the “main” platform domain (unless they also log in on that domain), and we’re not trying to change that. However, it IS important that:

  1. we can share the auth between the old Framework site and the new Core site even for these WLDs, and
  2. any requests that are forwarded back to the Framework app MUST be done using the WLD, because we do interogate the request URL for determining certain functionalities and display rules.

The shared auth thing was a bit tricky, but I solved that by finding a way to set the cookie domain dynamically based on each request that came in (if you need to know a solution, ping me on Twitter and I’ll write up a post sooner, I do plan to do it in the next couple of weeks).

I wasn’t able to find any nice way in the code to configure YARP to use a forwarding URL that was based on the incoming request URL, but I did find a way to configure it “once per WLD” in the config, and I’ve decided I’m OK with this since it’s only a temporary stop-gap, anwyay, until everything is migrated to .NET Core. We only onboard new white-label domains every few weeks, anyway, so it’s not too hard to add this extra config each time.

So without further ado, here’s how I updated the config to make this work (and thanks for your patience if you read all that just to get to this!!):

"ReverseProxy": {
  "Routes": {
    "fallbackRoute": {
      "ClusterId": "fallbackClusterMainPlatform",
      "Order": "1",
      "Match": {
        "Hosts": [ "mainplatform.local" ],
        "Path": "{**catch-all}"
      }
    },
    "fallbackRouteWhiteLabelDomain": {
      "ClusterId": "fallbackClusterWhiteLabelDomain",
      "Order": "1",
      "Match": {
        "Hosts": [ "whitelabeldomain.local" ],
        "Path": "{**catch-all}"
      }
    }
  },
  "Clusters": {
    "fallbackClusterMainPlatform": {
      "Destinations": {
        "fallbackApp": {
          "Address": "https://framework.mainplatform.local"
        }
      }
    },
    "fallbackClusterWhiteLabelDomain": {
      "Destinations": {
        "fallbackApp": {
          "Address": "https://framework.whitelabeldomain.local"
        }
      }
    }
  }

What we’ve done is set up one route per domain, and then one cluster per domain, and we match them up using the ClusterId.

With this configuration in place, I was able to get this working so that mainplatform.local/whatever would be proxied to framework.mainplatform.local/whatever (if the “whatever” route hadn’t been built yet in the Core app), and simiarly whitelabeldomain.local/whatever would be proxied to framework.whitelabeldomain.local/whatever.

(the .local TLD is just what I use for my local dev).

Now I’m just currently waiting on a response from the Reverse Proxy dev team about one outstanding issue that I’ve logged on Github, which is related to having custom routes set up and working at the same time as having a default controller routes setup. Once that’s done, I think I’ll be ready to go live with this beast, and start strangling some figs!!