Adding Custom Domains to your SaaS
Warren Parad

Warren Parad @wparad

About: Long time software architect, CTO Authress, creating application security plug-ins for any software application with Authress. Talk to me about security in microservices or service authorization.

Location:
Switzerland
Joined:
Jul 22, 2018

Adding Custom Domains to your SaaS

Publish Date: Feb 25 '22
33 15

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


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 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 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)

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
Enter fullscreen mode Exit fullscreen mode
  • 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.
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

AND

Name: customer-A.product-service.com
Value: dmv3o3npogaoea.cloudfront.net
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

AND

Name: example.com (their custom domain)
Value: customer-A.product-service.com
Enter fullscreen mode Exit fullscreen mode

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

Image description

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

Image description

Image description

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 and discuss this and other security related topics!


Join the community

Comments 15 total

  • Corentin
    CorentinApr 11, 2022

    Just sharing that CloudFlare also offers something similar within their CloudFlare for SaaS offering. It's now available with all plans (including their free plan). Price is 100 custom domains for free, then $0.10/month per additional custom domain. Definitely a no-brainer coming from such a trusted brand.

    • Warren Parad
      Warren ParadMay 3, 2022

      Absolutely true, although Cloudflare used to charge $5000/month just to get access to the functionality. There are lots of providers for doing it, this is only one valid way and basically free.

    • Fabio Moretti
      Fabio MorettiJun 10, 2022

      What exact offering of cloudflare are you referencing?

        • Alex Blokh
          Alex BlokhAug 15, 2022

          it does seem like it starts from free

          • Corentin
            CorentinAug 17, 2022

            It does! Cloudflare for Saas has a free plan which includes up to 100 custom domains

            • Alex Blokh
              Alex BlokhAug 18, 2022

              yeah, for some reason it is extremely unclear with their pricing page
              I think they're in a hurry and have to spend some time and refactor it a bit

  • АнонимJan 11, 2023

    [hidden by post author]

  • amadeo
    amadeoFeb 17, 2023

    Very nice article! thanks a lot for the clear explanation.

    I am considering implementing this but I am concerned about the limit in distributions per account (200), what if I have thousands of customers? I have read about another solution with just one cname pointing to a load balancer which terminates the SSL connection. Any thoughts?

    Thank you!!

    • Warren Parad
      Warren ParadFeb 19, 2023

      That's just the default limit: docs.aws.amazon.com/AmazonCloudFro...

      You don't want to spin up a whole load balancer per customer that is super expensive, and actually the default limit for ALBs is much lower at 50: docs.aws.amazon.com/elasticloadbal...

      • amadeo
        amadeoFeb 22, 2023

        Hi Warren, thanks for the answer.

        Let me clarify,
        I don't mean one ELB per customer, but one for all customers. Here the limit is the ammount of certificates per rule in ELB (which is 25 default) although one could pack many in one (ELB supports SAN).
        There is the possibility of doing this with nginx + certbot and have no limit.

        Somehow (independenly of the service, Cfront, ELB, whatever) configuring and mantaining one per customer feels a bit too much for me.

        May be I am just scared XD

  • АнонимJun 8, 2023

    [hidden by post author]

  • АнонимJun 20, 2023

    [hidden by post author]

  • Fredrick Reuben
    Fredrick ReubenAug 19, 2024

    Great article! Here’s how my implementation aligns with what you described: I create CloudFront distributions and SSL certificates for each user as outlined. For domain masking, I use CloudWatch to notify my application when an SSL certificate is issued. My software then automatically updates CloudFront with the custom domain and the newly issued SSL certificate. All values are stored on my server. I would love to here your thoughts on this as well

    • Warren Parad
      Warren ParadAug 25, 2024

      The trouble with using CloudWatch events is that there are many more states to deal with than just "Issued", and there are likely many more resources than just the certificate. In these circumstances, you might want to perform any number of async retries or updates or notifications for the user to get them to complete the validation. So I don't recommend CloudWatch, but instead a Step Function which controls the exact timing of Wait Steps, Retries, and notification Lambda steps to actually do this.

Add comment