Adding Custom Domains to your SaaS
Tags: security, aws, saas, architecture
Published: 2022-02-25 11:17:26

---
title: Adding Custom Domains to your SaaS
published: true
description: 
tags: security,aws,saas,architecture
//cover_image: https://direct_url_to_image.jpg
---

You're building out a SaaS solution and realize for one reason or another supporting **custom domains** for your customers is a must. There are some products out there that can do this for you in some way, but they either cost a lot, are a huge maintenance burden or aren't scalable. Here, we'll walk through using AWS to add custom domains to your solution.

***

## What are Custom Domains?

First, let's discuss what a **custom domain** actually is. A custom domain, let's your customer brand your API, UI, service, or product with their domain on top of your service. This provides a huge amount of value for both parties. For customers they can provide your service to their third parties or hide the implementation detail of what they are using. No one wants to expose their dependencies to customers, it's unnecessary and dangerous.

For example if you have a product @ https://product-service.com and offer custom domains for your customers, they may look like:

`https://customer-A.product-service.com`

or

`https://02c77286.product-service.com`

This might be fine in a lot of situations, you can give the customer this identifier or url and call it a day. You might even have some logic which would associate this with their user/account to automatically redirect them. That's all good, but in the first case, let's say you want to include a **Support Portal** as part of your **Product Service**, you don't want to redirect your customers to `https://customer-A.third-party-website.com`, you want them to stay on the same domain.

As a concrete example, when you sign up for our product you get an account ID that looks like `bwdlb5r89jwhy232`, so the urls look like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m0qxeen8b0cwfgo5hocd.png)
 

***

## So why are we doing this again?

Just like the support portal option, custom domains have two other important uses.

#### Internal benefit

One example is your benefit. We'll use our product [Authress](https://authress.io) as an example. Authress provides an API for access control. That means every customer has their user identities, resources, roles, and permissions managed by the service. Since Authress optimizes permission queries as well as user credential security and caching, segregating each customer to their own subdomain has a lot of value for us. Authress charges by API call, that means tracking of these calls is critical, using the subdomain is a great way to separate calls.

When the service allocates a subdomain to an account, the subdomain matches the accountId:

`https://accountId-A1.api.authress.io`

And while this is easy for developers to use in a service, it isn't pretty. So we provide the capability to mask this domain with one the customer chooses.

#### Security implications

Security is improved via:

A) There are lots of restrictions that exist on a per domain basis, CORS/Cookies are one of them. Allowing your users to set their custom domain for your product to match their domain, allows them to consume these resources in a safe way. Having different domains may tempt you to store some things that you shouldn't.

B) User SSO and authentication. For SSO, every business customer will want to have their own login provider (IdP) utilized. That means Account 1 uses IdP A, Account 2 uses IdP B, and so on. There is no way to know before a user logs in which IdP to use, so we need to guess. We could guess by forcing the user to enter their email, and then using the `domain` of the email.

This is a terrible solution. Instead, let the customer use a custom domain on top of your subdomain to configure their IdP. Then you can just look at the domain to determine which IdP to you. (Spoiler: this is exactly what Authress does to automatically support SSO for B2B products, you don't need to do any extra work, if implemented through Authress connections).

***

## Setting up a custom domain

So you want to let your customers, tell you which domain they want and then you map

`https://custom.domain.com => https://customer-A.product-service.com`

This is mostly a DNS record, and you are done. All they have to do is add **CNAME** added to their Domain Registrar's hosted zone.

Sounds easy doesn't it? So why do all these companies try to charge you a ton for this?

***

## The Challenge

While the CNAME works out of the box, the real problem comes with TLS or HTTPS. In order for that CNAME to work and for their users to correctly have encrypted traffic, **YOUR** service needs to serve a TLS certificate that matches **THEIR** domain.

If this was your domain, you would add a DNS verification to your Hosted Zone, and your cert would work. Or You could [AWS Certificate Manager](https://aws.amazon.com/certificate-manager/) and this would work without even any code.

But you don't own this domain, which means somehow you need them to give you a cert, and this cert has to be renewable, that means every 3 months~1 year you would need a new cert...For every customer...

This also means maintaining a database of these encrypted certs and safe-guarding them, because if you:
* lose them, then all your customers can no longer access your site
* expose access to them, then anyone can impersonate your customers

Both of these are really bad.

***

## The Solution

(A small aside: If you actually wanted to pay for this, there is a discussion about all the different options available on [IndieHackers](https://www.indiehackers.com/post/how-to-implement-custom-domains-support-for-my-saas-product-214828c6ca))

As I mentioned there are tons of solutions to this out in the world, but none of them are great, they are costly to maintain, and worse, if there is something that goes wrong, it will go really wrong. So here I'm going to outline how you can do with this AWS Certificate (ACM) and AWS CloudFront (CF) and not have to worry about anything else.

### 1. Set up your service
Run your service like you would, wherever you would, this could be AWS Lambda or ECS (or if you are a masochist--EC2, or just want to burn a lot of money for no reason--EKS).

### 2. Support wildcard processing
Your service needs to work with wildcards or genericly specified domains. This means that `https://*.product-service.com` needs to be handled correctly by your stack. For APIGW, this means adding a custom domain for AWS APIGW that includes the `*`. For a static site running in S3, however, you don't need any extra magic.

### 3. Enable custom domain requests
Let your customer request "I want to setup a custom domain, my domain is `example.com`". Generally speaking, require them to use a subdomain, because you likely aren't hosting their main website. For Authress, we require subdomains lik `login.example.com`.

### 4. Deploy Customer Account specific resources
To do this we'll deploy a new CloudFront and a new Certificate, both in the US-EAST-1 region (this is important). Both resources should request resources with the custom domain specified.

* Create a new CloudFront Distribution with **No** alternate domain name set. (If you try to use the cert before it is validated it will fail, so don't do this). This distribution should point to your existing infrastructure, and have custom headers set for the specific customer accountId/subdomain. The CF distribution will have a domain like:
```
dmv3o3npogaoea.cloudfront.net
```

* Request a new ACM certificate with the custom domain and get back the DNS validation options, they look like this:
```
Name: _85dd6f5e25429205f5ffa77.example.com.
Value: _caee56563101.aocomg.acm-validations.aws.
```

### 5. Mask these values so you can make updates later, by creating two CNAME records in your hosted zone:
```
CNAME: validation-customer-A.product-service.com
Value: _caee56563101.aocomg.acm-validations.aws (value from above)
```
AND
```
Name: customer-A.product-service.com
Value: dmv3o3npogaoea.cloudfront.net
```

### 6. Return these two sets of DNS CNAME values:
```
Name: _85dd6f5e25429205f5ffa77.example.com (Name from above)
Value: validation-customer-A.product-service.com (our custom name)
```
AND
```
Name: example.com (their custom domain)
Value: customer-A.product-service.com
```

### 7. Wait for your customer to add these two CNAME records to their domain hosted zone.

Concretely, from our example it would looks like this:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ks79wduxhuobnpdfs1pa.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8v2fyo2e2o7dar3yp5eg.png) 

### 8. Once verified update your CloudFront distribution alternate domain name and certificate that was just verified.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aytwbu4156hzgbmwkn90.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q9aplk34peqtva73rky2.png)
  

Done.

***

## The Result
Now you should be successfully running the CloudFront distribution as a TLS proxy for your service, website, app, etc... It will allow the white labeling that your customers need and you don't need to pay a fortunate to managing anything. ACM and ACF are free, and all the traffic you would pay for anyway just gets migrated to a new CF.

Even better? you can attach protections to your infrastructure to monitor the usage of these proxies to know how they are being used. And further, all the tools exist at the CF level for rate limiting, DDoS protection, real-time logging, custom assets, and the list goes on.

***

Come join our [Community](https://authress.io/community/) and discuss this and other security related topics!