Project

Subdomain Based Multi Tenancy in ASP.NET Zero (Angular)

Introduction

This document provides detailed guidance for configuring and troubleshooting subdomain based multi tenancy in ASP.NET Zero projects utilizing an Angular frontend. It expands upon the foundational knowledge offered in the official documentation, aiming to cover advanced configurations, development environment strategies, and common issues.

This document assumes you have a basic understanding of ASP.NET Zero's multi tenancy concepts and have reviewed the main Overview Angular documentation.

1. Configuration for Subdomain Based Tenancy

This section involves SSL management, web server setup, and specific Angular application settings.

SSL Certificate Management for Wildcard Domains

  • Requirement: For HTTPS, a wildcard SSL certificate (e.g., for *.mydomain.com) or a Subject Alternative Name (SAN) certificate covering all required tenant subdomains is essential. This secures communication for all your tenants.
  • Setup:
    • Obtain a wildcard or appropriate SAN certificate from a Certificate Authority (CA).
    • Install the certificate on your web server (e.g., IIS, Nginx, Apache) or load balancer.
    • For IIS, this involves binding the HTTPS protocol (port 443) to the installed wildcard certificate for your application's site.
  • Note on ClientRootAddress and ServerRootAddress: If your ClientRootAddress for tenants (e.g., https://{TENANCY_NAME}.app.mydomain.com/) and ServerRootAddress (e.g., https://{TENANCY_NAME}.api.mydomain.com/) use different primary domains or distinct sets of subdomains, ensure your SSL certificate(s) cover all relevant wildcard domains (e.g., *.app.mydomain.com and *.api.mydomain.com).

Web Server Detailed Configuration (Example: IIS)

  • Host Headers & Bindings:
    • Configure a single website in IIS to respond to requests for all tenant subdomains.
    • For HTTP binding (port 80), you can often leave the "Host Name" field empty if it's the only site on that IP, or specify each primary domain if needed.
    • For HTTPS binding (port 443), select the appropriate wildcard SSL certificate. The host name field might be left blank or set if your IIS version supports specific SNI configurations with wildcards, but often the wildcard nature is primarily handled by the certificate itself. The key is that IIS listens for traffic on the IP and port, and the certificate validates for *.mydomain.com.
  • Default Document/Application: Ensure your IIS site is configured to correctly serve the Angular application (typically index.html and other static assets from your dist folder) for all subdomain requests that point to the client application.
  • URL Rewrite (If Necessary): While ASP.NET Zero's framework handles tenant resolution based on the host, complex infrastructures involving reverse proxies might require URL Rewrite rules. However, for standard deployments, this is generally not needed for the core multi tenancy functionality.

Angular Application (appsettings.json) Specifics

  • Dynamic Configuration with {TENANCY_NAME}:
    • It is critical that the {TENANCY_NAME} placeholder is used in the ClientRootAddress and ServerRootAddress within your Host application's YourProjectName.Web.Host/appsettings.json (and potentially appsettings.Production.json).
    • Example:
      {
          "App": {
              "ServerRootAddress": "https://{TENANCY_NAME}.api.mydomain.com",
              "ClientRootAddress": "https://{TENANCY_NAME}.app.mydomain.com",
              //... Other Settings
          }
      }
      
  • Tenant Resolution in Angular:
    • ASP.NET Zero's client side infrastructure typically uses the ServerRootAddress. The framework includes logic to dynamically replace the {TENANCY_NAME} placeholder based on the current browser URL's subdomain.
    • This ensures that API calls generated by NSwag or other service proxies are directed to the correct tenant specific backend endpoints.
    • While largely abstracted, the Angular application inherently becomes tenant aware through these configured, dynamic URLs.

2. Development Environment Strategies for Subdomain Testing

Testing subdomain based multi tenancy locally requires a few extra steps compared to path based tenancy or using the tenant switch dialog.

Using the hosts File

  • Purpose: To simulate DNS resolution for your subdomains on your local machine, you can edit the hosts file. You can back up the file before making any changes.
    • Windows: C:\Windows\System32\drivers\etc\hosts
    • Linux/macOS: /etc/hosts
  • Example: Add entries to map your desired local subdomains to your loopback address (127.0.0.1):
    127.0.0.1 tenant1.localhost
    127.0.0.1 tenant2.localhost
    127.0.0.1 api.localhost # If testing backend with a similar local subdomain
    
  • Accessing the App: After saving the hosts file (you might need administrator privileges), you can access your Angular application in the browser using URLs like http://tenant1.localhost:4200 (or your configured Angular development port).

Development Web Server Configuration

  • Angular CLI (npm run start):
    • The Angular CLI's development server (npm run start) by default listens on localhost. To make it respond to custom hostnames like tenant1.localhost (which your hosts file now points to 127.0.0.1), you might need to use:
      • npm run start (ng serve --host 0.0.0.0): This makes ng serve listen on all available network interfaces.
      • --disable-host-check: In some cases, you might also need to add --disable-host-check (e.g., ng serve --host 0.0.0.0 --disable-host-check). Use this flag with caution as it disables a security feature.
  • ASP.NET Core Host (YourProjectName.Web.Host):
    • Similarly, ensure your Kestrel server (used by the ASP.NET Core backend) is configured to listen on the appropriate hostnames and ports if you are testing it with local subdomains. This can often be configured under the applicationUrl property in launchSettings.json for your development profile (e.g., http://api.localhost:44301).

Local SSL with Subdomains

  • Standard localhost development certificates provided by dotnet dev-certs https will not be valid for custom local subdomains like tenant1.localhost. This will result in browser SSL warnings.
  • Solutions:
    1. HTTP Locally: For simplicity, you can develop and test the subdomain logic using HTTP locally and rely on the tenant switch dialog if HTTPS specific features are not being tested.
    2. mkcert Tool: Use a tool like mkcert to create a locally trusted Certificate Authority (CA) and then generate wildcard certificates (e.g., *.localhost). You would then configure Kestrel and potentially the Angular dev server to use these certificates. This provides a more accurate simulation of a production HTTPS environment.

Interaction with Tenant Switch Dialog

  • When subdomains are correctly configured and resolved (even in a local development environment using the hosts file), ASP.NET Zero should automatically identify the tenant from the URL's subdomain.
  • In such cases, the manual 'Tenant Switch' dialog becomes less relevant for requests made to these subdomain URLs, as the tenant context is already established.

3. Tenant Resolution Flow Clarification

A clear understanding of the tenant resolution process is essential to ensure accurate tenant identification and proper request routing in a multi tenant architecture. This step determines which tenant context should be applied based on the incoming request, typically using elements like the subdomain, header, or query string.

Backend (ASP.NET Core)

  1. An incoming HTTP request arrives at the ASP.NET Core application.
  2. ASP.NET Zero's middleware, specifically implementations of ITenantResolveContributor (like DomainTenantResolveContributor), inspects the request's host header (e.g., tenant1.api.mydomain.com).
  3. If the host matches the configured subdomain pattern (derived from ServerRootAddress with the {TENANCY_NAME} placeholder), the middleware extracts the tenancy name (tenant1 in this example).
  4. It then attempts to find this tenant in the database.
  5. If found, the current tenant context is set for the duration of that request, ensuring data isolation and that tenant specific settings and services are used.

Frontend (Angular)

  1. The Angular application is loaded from a URL like https://tenant1.app.mydomain.com.
  2. The ServerRootAddress in appsettings.json (e.g., https://{TENANCY_NAME}.api.mydomain.com) is used by the generated service proxies.
  3. ASP.NET Zero's client side JavaScript utilities dynamically replace the {TENANCY_NAME} placeholder in ServerRootAddress with the actual tenancy name extracted from the current browser window's hostname (tenant1.app.mydomain.com).
  4. All subsequent API calls made through these service proxies will correctly target the tenant specific backend API endpoints (e.g., https://tenant1.api.mydomain.com/api/services/app/User/GetUsers).

4. Troubleshooting Common Subdomain Multi Tenancy Issues

Here are some common problems and how to address them:

DNS Not Resolving

  • Propagation Time: DNS changes (especially for new wildcard records) can take time to propagate globally (minutes to hours).
  • Record Verification: Double check your DNS provider's settings. Ensure the A record or CNAME record for the wildcard (e.g., *.mydomain.com) correctly points to your web server's public IP address.
  • Local hosts File: For local development, ensure your hosts file entries are correctly spelled, saved, and that your browser or OS is not caching old DNS lookups aggressively (try clearing cache or an incognito window).

CORS Errors

  • CorsOrigins Configuration: In the appsettings.json of your *.Web.Host project, meticulously check the App:CorsOrigins setting. It must accurately include the URLs of your Angular application, including the wildcard for subdomains.
    • Example: "CorsOrigins": "https://*.app.mydomain.com,http://*.app.mydomain.com" (include both HTTP and HTTPS if applicable, and consider ports if non standard).
  • Browser Console: Always inspect the browser's developer console (Network tab) for detailed CORS error messages. The error often indicates which origin is being blocked and what headers are missing or mismatched from the preflight (OPTIONS) request.
  • Scheme and Port: CORS is sensitive to scheme (HTTP vs. HTTPS) and port numbers. Ensure these match exactly in your CorsOrigins configuration.

SSL Certificate Errors

  • Certificate Name Mismatch: This means the SSL certificate presented by the server is not valid for the domain requested by the browser.
    • Ensure your certificate is a true wildcard (e.g., *.mydomain.com) or a SAN certificate that explicitly lists all required tenant subdomains or the relevant wildcard.
  • Untrusted Certificate:
    • For production, ensure your certificate is issued by a reputable CA.
    • For local development with self signed or mkcert generated certificates, ensure the root CA certificate used to sign them is trusted by your operating system and browser.
  • Mixed Content: Ensure all resources are loaded over HTTPS if your site is HTTPS.

Incorrect Tenant Loaded or Redirected to Host

  • URL Configuration Check:
    • Verify ClientRootAddress and ServerRootAddress in YourProjectName.Web.Host/appsettings.json.
    • Ensure they all correctly use the {TENANCY_NAME} placeholder and reflect your actual domain structure.
  • PreventNotExistingTenantSubdomains:
    • Check this setting in YourProjectNameConsts.cs (in the Core.Shared project) for the API.
    • Check the equivalent setting in AppConsts.ts for the Angular application.
    • If true, requests to subdomains for tenants that don't exist will be handled differently (often redirected or result in an error), rather than defaulting to the host.
  • Tenant Existence: Ensure the tenancy name being extracted from the URL (e.g., tenant1 from tenant1.mydomain.com) actually exists in your application's database and is active.
  • ASP.NET Core Logging: Increase logging verbosity for ASP.NET Core, especially around tenant resolution, to see how the tenant is being identified (or failing to be identified).

Contributors


Last updated: May 14, 2025 Edit this page on GitHub
In this document