ASP.NET Core CORS Wildcard Subdomains

Posted by ryansouthgate on 22 Jun 2023

This post will demonstrate how to setup wildcard subdomains for your CORS config in ASP.NET Core

The Situation

Recently, a company I work for has been exploring the idea of starting a SaaS business model. Without going too far into the details on this, essentially they are offering a Platform for their users (with their own branding/subdomain).

The backend is written in ASP.NET Core. There’s lots of Terraform which sets up the environments/services in AWS etc. Terraform also creates the CNAME records for each of the new SaaS tennats with their own, unique subdomains.

The Platform is an Angular SPA, which communicates with an ASP.NET Core API. There are currently some rules on which specify which domains are allowed to make calls to this API. This is one of many forms of restriction to make it more difficult for “bad-actors” to mis-use the API, and call it from different/their own domains.

CORS

CORS (Cross Origin Resource Sharing) guards (in our case) our API from being accessed from other domains that we might not own. If I’m hosting a website and an API on https://ryansouthgate.com so readers can query blog posts in JSON format. I don’t want the creator of https://fakeryansouthgate.com being able to access my blog post API, to impersonate me. With CORS, I can specify that only web pages which are loaded from https://ryansouthgate.com can make calls to my API.

Here’s the Wikipedia page on CORS, which gives a more thorough explanation Cross-origin Resource Sharing - Wikipedia

This protection happens at the browser level, and it is possible to still call that API from Postman/curl - and you’ll get a valid response. There are also browser extensions which will disable CORS - these are great for testing and diagnosing problems with your CORS setup.

The Problem

Currently, the allowed CORS domains are listed in a config file and loaded as part of the ASP.NET Core startup process. With a small number of domains, this is a good and clean way to have an “allow-list” of domains which can access the API.

As the number of SaaS tennants is starting to grow (in the future there will likely be hundreds/thousands), this will mean our config file (which stores other values, not relating to CORS), would grow to become quite the beast. Below is a similar example which, instead of using a config file, shows the domains in Startup.cs:

public virtual void ConfigureServices(IServiceCollection services)
{
    //...other ASP.NET Setup

    services.AddCors(options =>
            {
                options.AddDefaultPolicy(
                    builder => builder
                    .WithOrigins
                    (
                        "https://client-a.ryansouthgate.com",
                        "https://client-b.ryansouthgate.com",
                        "https://client-c.ryansouthgate.com",
                        "https://client-d.ryansouthgate.com",
                        "https://client-e.ryansouthgate.com",
                        "https://client-f.ryansouthgate.com",
                        "https://client-g.ryansouthgate.com",
                        "https://client-h.ryansouthgate.com"
                    )
                    .AllowAnyMethod()
                    .AllowAnyHeader());
            });
}

With the example above in mind, if I created a new website on https://owner-abc.ryansouthgate.com and which tried to access my API, it wouldn’t work. Looking in the browser’s developer tools console window (and network tab) you’d see loads of CORS errors - the API calls would just fail.

We could split out the CORS Domain list above into a different file and load that separately, but that still doesn’t satisfy my need to remove lots of config and having to remember to update different lists when a new SaaS client is onboarded. I want something cleaner, something more elegant.

Solution

The Solution is to use a wildcard instead of explicitly listing the subdomains.

The example above can be changed to:

public virtual void ConfigureServices(IServiceCollection services)
{
    //...other ASP.NET Setup

    services.AddCors(options =>
            {
                options.AddDefaultPolicy(
                    builder => builder
                    .WithOrigins
                    (
                        "https://*.ryansouthgate.com"
                    )
                    // This method call is important!
                    .SetIsOriginAllowedToAllowWildcardSubdomains()
                    .AllowAnyMethod()
                    .AllowAnyHeader());
            });
}

which will allow all subdomains for “ryansouthgate.com” to access resources on this API. For example, some of these below will now work, with minimal configuration:

And so on…

Note: Ensure the method call SetIsOriginAllowedToAllowWildcardSubdomains() is included as without this, your wildcard domains wont be recognised by the ASP.NET Core CORS system

Closing

And that’s it! We’ve kept our code and configuration clean. Without being overly verbose and having to maintain multiple large lists of allowed subdomains, we’ve managed to allow all subdomains on our root domain to access the API.



comments powered by Disqus