Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Subdomain Routing

Aero2 uses subdomain-based routing to serve multiple applications from a single Cloudflare Worker deployment. Each application gets its own subdomain for proper cookie, CORS, and issuer isolation.

URL Structure

dashboard.aero2.dev          -> Developer Dashboard (sign up, create apps, manage)
swift-maple.aero2.dev           -> App's auth pages + API (end-user facing)
brave-falcon.aero2.dev           -> App's auth pages + API
auth.myapp.com            -> Custom domain (CNAME -> swift-maple.aero2.dev)
aero2.dev                    -> Marketing / landing page

Why Subdomains (Not Paths)

  • Cookie isolation: Each app gets its own cookies naturally (security requirement). No risk of cross-app cookie leakage.
  • CORS isolation: Each app is its own origin, preventing cross-app API calls.
  • Custom domains: Easy CNAME mapping via Cloudflare for SaaS.
  • OIDC issuer: Each app has a unique issuer URL (https://{slug}.{domain}).

Worker Routing Logic

The subdomain routing middleware parses the Host header and resolves the application context:

Incoming request
|
+-- Host: aero2.dev
|   -> Marketing landing page / redirect to dashboard
|
+-- Host: dashboard.aero2.dev
|   -> Developer Dashboard (built-in app, app_id = DASHBOARD_APP_ID)
|   -> Serve dashboard SPA + dashboard API
|
+-- Host: swift-maple.aero2.dev
|   -> DB lookup: SELECT * FROM applications WHERE slug = 'swift-maple'
|   -> Set app_id context on request
|   -> Serve that app's branded auth pages + API
|
+-- Host: auth.myapp.com (custom domain)
|   -> DB lookup: SELECT * FROM applications WHERE custom_domain = 'auth.myapp.com'
|   -> Same as above
|
+-- Host: unknown.aero2.dev
    -> 404: "Application not found"

Middleware Implementation

The tenant middleware (src/backend/middleware/tenant.ts) performs these steps:

  1. Parse hostname from Host header
  2. Extract slug (first subdomain segment)
  3. If slug equals DASHBOARD_SLUG environment variable, use dashboard app_id
  4. Otherwise, query D1: SELECT * FROM applications WHERE slug = ? OR custom_domain = ?
  5. If not found, return 404 "Application not found"
  6. Set c.set('app', applicationRow) for downstream handlers

Application Lookup Caching

To avoid a D1 query on every request, application lookups are cached in an in-memory Map with a 60-second TTL:

  • Cache key: hostname (subdomain or custom domain)
  • Cache value: full application row
  • TTL: 60 seconds
  • Invalidation: on application update or delete

This keeps cold-start database lookups to a minimum while ensuring changes propagate within one minute.

Per-App CORS

Each application's subdomain is its own origin. The CORS middleware dynamically sets the allowed origin:

  • Dashboard app allows https://dashboard.{PLATFORM_DOMAIN}
  • Each app allows https://{slug}.{PLATFORM_DOMAIN}
  • If the app has a custom_domain, that origin is also allowed

Per-App Cookies

Cookie Domain is set to the app's specific subdomain (not a wildcard). This ensures browser-level cookie isolation:

  • dashboard.aero2.dev cookies are only sent to dashboard.aero2.dev
  • swift-maple.aero2.dev cookies are only sent to swift-maple.aero2.dev
  • No wildcard .aero2.dev cookies that would leak across apps

Per-App OIDC Issuer

Each application has its own OIDC issuer URL:

  • Issuer: https://{slug}.{PLATFORM_DOMAIN} (e.g., https://swift-maple.aero2.dev)
  • If a custom domain is configured and verified: https://{custom_domain} (e.g., https://auth.myapp.com)
  • OIDC Discovery at /.well-known/openid-configuration returns the per-app issuer
  • JWKS at /oauth2/jwks.json is shared but token iss claim uses per-app issuer
  • Token verification checks iss against the requesting app's issuer

Custom Domains via Cloudflare for SaaS

Custom domains use Cloudflare's Custom Hostnames feature:

  1. Developer adds a custom domain in the dashboard (e.g., auth.myapp.com)
  2. Aero2 calls the Cloudflare API to create a Custom Hostname:
    POST /zones/{zone_id}/custom_hostnames
    { hostname: "auth.myapp.com", ssl: { method: "http", type: "dv" } }
  3. SSL is auto-provisioned by Cloudflare
  4. Developer adds a CNAME record: auth.myapp.com -> swift-maple.aero2.dev
  5. Traffic flows through Cloudflare to the same Worker
  6. The tenant middleware resolves the custom domain to the correct application

Required Environment Variables

VariablePurpose
PLATFORM_DOMAINBase domain (e.g., aero2.dev)
DASHBOARD_SLUGSlug for the dashboard app (e.g., dashboard)
CF_ZONE_IDCloudflare zone ID for custom hostname API calls
CF_API_TOKENCloudflare API token with Custom Hostnames permission

DNS Configuration

Type    Name    Content         Proxy
A       @       192.0.2.1       Proxied    (bare domain, placeholder IP)
CNAME   *       aero2.dev       Proxied    (wildcard catches all subdomains)

Worker Route Configuration

In wrangler.json:

{
  "env": {
    "production": {
      "routes": [
        { "pattern": "aero2.dev/*", "zone_name": "aero2.dev" },
        { "pattern": "*.aero2.dev/*", "zone_name": "aero2.dev" }
      ],
      "vars": {
        "PLATFORM_DOMAIN": "aero2.dev",
        "DASHBOARD_SLUG": "dashboard"
      }
    },
    "qa": {
      "routes": [
        { "pattern": "qa.aero2.dev/*", "zone_name": "aero2.dev" },
        { "pattern": "*.qa.aero2.dev/*", "zone_name": "aero2.dev" }
      ],
      "vars": {
        "PLATFORM_DOMAIN": "qa.aero2.dev",
        "DASHBOARD_SLUG": "dashboard"
      }
    }
  }
}