Skip to content

feat(examples): add Multiple Custom Domains (MCD) web example#80

Open
frederikprijck wants to merge 1 commit into
mainfrom
worktree-examples
Open

feat(examples): add Multiple Custom Domains (MCD) web example#80
frederikprijck wants to merge 1 commit into
mainfrom
worktree-examples

Conversation

@frederikprijck

@frederikprijck frederikprijck commented Jun 19, 2026

Copy link
Copy Markdown
Member

Description

Adds a Multiple Custom Domains (MCD) example for @auth0/auth0-fastify.

A single Fastify app serves two hostnames — brand-a.localhost and brand-b.localhost — on one port, each mapped to a different Auth0 custom domain of the same Auth0 tenant (custom domains that resolve to one underlying tenant at the DNS level). It uses a single plugin registration configured with a domain resolver function instead of a static domain string.

MCD is about multiple custom domains of a single Auth0 tenant — not connecting multiple tenants to one app.

What's included

  • examples/example-fastify-web-mcd/ — runnable example: src/index.ts (host → custom-domain resolver, per-domain discoveryCache), EJS views showing the serving host + resolved Auth0 domain, README, and .env.example.
  • Testssrc/index.spec.ts (vitest + msw + fastify.inject, matching the SDK's own test conventions) covering per-host domain resolution and default-domain fallback. 3/3 passing.
  • CI — adds a Multiple Custom Domains job to .github/workflows/examples.yml (build + test:ci), alongside the existing web and api example jobs.
  • Root README.md — links the new example.

Testing

  • npm run build -w @auth0/auth0-fastify
  • npm run build -w example-fastify-web-mcd
  • npm run test:ci -w example-fastify-web-mcd ✅ (3 tests)

Security

The example and README document the MCD security requirements: the resolver must only ever return trusted custom domains of your tenant, and the app must run behind a reverse proxy that sanitizes Host/X-Forwarded-Host.

Summary by CodeRabbit

  • New Features

    • Added a new Fastify web application example supporting multiple custom domains with Auth0 authentication, including comprehensive documentation and configuration templates.
  • Documentation

    • Updated main README with reference to the new example.
  • Tests & CI

    • Added automated testing workflow for examples via GitHub Actions.

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@frederikprijck, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 34 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6c8a476e-0f76-4332-8210-1c1cc8f4d012

📥 Commits

Reviewing files that changed from the base of the PR and between b029041 and ec81908.

⛔ Files ignored due to path filters (1)
  • examples/example-fastify-web-mcd/public/img/auth0.png is excluded by !**/*.png
📒 Files selected for processing (13)
  • .github/workflows/examples.yml
  • README.md
  • examples/example-fastify-web-mcd/.env.example
  • examples/example-fastify-web-mcd/README.md
  • examples/example-fastify-web-mcd/package.json
  • examples/example-fastify-web-mcd/src/index.spec.ts
  • examples/example-fastify-web-mcd/src/index.ts
  • examples/example-fastify-web-mcd/tsconfig.json
  • examples/example-fastify-web-mcd/views/index.ejs
  • examples/example-fastify-web-mcd/views/layout.ejs
  • examples/example-fastify-web-mcd/views/private.ejs
  • examples/example-fastify-web-mcd/views/public.ejs
  • llms.txt
📝 Walkthrough

Walkthrough

A new example-fastify-web-mcd package is added under examples/, demonstrating Auth0 Multiple Custom Domains (MCD) resolver mode with Fastify. It includes the full project scaffold, a server that maps request hostnames to Auth0 custom domains with env-driven configuration, EJS views, Vitest integration tests using MSW, a CI workflow, and documentation.

Changes

Fastify MCD Example

Layer / File(s) Summary
Project scaffolding and config
examples/example-fastify-web-mcd/package.json, examples/example-fastify-web-mcd/tsconfig.json, examples/example-fastify-web-mcd/.env.example
Sets up the ESM package with start/build/test scripts, TypeScript compiler options (NodeNext, strict, declarations), and an .env.example template for Auth0 credentials and two custom domain slots.
MCD server: app setup and domain resolver
examples/example-fastify-web-mcd/src/index.ts
Creates and exports the Fastify instance, registers static/EJS plugins, builds a host→Auth0 domain map from env vars, implements resolveAuth0Domain with AUTH0_DOMAIN fallback, defines a DomainResolver, and registers fastifyAuth0 in resolver mode with session and discovery cache config.
Routes, session guard, and EJS views
examples/example-fastify-web-mcd/src/index.ts, examples/example-fastify-web-mcd/views/*.ejs
Defines /, /public, and /private routes (each fetching the current user and passing resolved domain to templates), adds hasSessionPreHandler to redirect unauthenticated requests, includes startup logic with a direct-execution guard, and provides Bootstrap-based EJS layout plus index/public/private page templates.
Vitest integration tests for domain resolution
examples/example-fastify-web-mcd/src/index.spec.ts
Mocks OIDC discovery endpoints via MSW for multiple tenant domains, dynamically imports the Fastify app after env injection, and asserts per-host 302 Location (tenant-1, tenant-2, default fallback) and correct redirect_uri origin for /auth/login.
CI workflow and documentation
.github/workflows/examples.yml, examples/example-fastify-web-mcd/README.md, README.md
Adds a GitHub Actions workflow to build @auth0/auth0-fastify and run example-fastify-web-mcd CI tests on PR/push/merge events; adds example README covering setup, hosts configuration, Auth0 tenant settings, testing steps, caching notes, and a security warning; adds a root README entry for the new example.

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant FastifyServer
  participant resolveAuth0Domain
  participant fastifyAuth0Plugin
  participant Auth0Tenant

  Browser->>FastifyServer: GET /auth/login (Host: app-tenant1.localhost)
  FastifyServer->>resolveAuth0Domain: hostname = "app-tenant1.localhost"
  resolveAuth0Domain-->>FastifyServer: AUTH0_CUSTOM_DOMAIN_1
  FastifyServer->>fastifyAuth0Plugin: DomainResolver returns AUTH0_CUSTOM_DOMAIN_1
  fastifyAuth0Plugin->>Auth0Tenant: fetch OIDC discovery for AUTH0_CUSTOM_DOMAIN_1
  Auth0Tenant-->>fastifyAuth0Plugin: discovery document
  fastifyAuth0Plugin-->>FastifyServer: 302 Location → AUTH0_CUSTOM_DOMAIN_1/authorize?redirect_uri=http://app-tenant1.localhost/...
  FastifyServer-->>Browser: 302 redirect to AUTH0_CUSTOM_DOMAIN_1
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 Hop, hop through domains so wide,
Two custom hosts on localhost hide.
Resolver picks the right Auth0 path,
No wrong tenant shall face my wrath!
MSW mocks the discovery call,
One Fastify app serves them all. 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a Multiple Custom Domains (MCD) web example for the Fastify Auth0 SDK.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-examples

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (1)
examples/example-fastify-web-mcd/src/index.spec.ts (1)

80-92: ⚡ Quick win

Add a host-without-port regression test.

Current suite misses the common Host: tenant-1.localhost path; adding it would lock in resolver behavior across proxy/header variations.

Suggested test addition
+  test('login from tenant-1 host without explicit port still resolves tenant-1 domain', async () => {
+    const res = await fastify.inject({
+      method: 'GET',
+      url: '/auth/login',
+      headers: { host: 'tenant-1.localhost' },
+    });
+    const location = new URL(res.headers['location']?.toString() ?? '');
+
+    expect(res.statusCode).toBe(302);
+    expect(location.host).toBe(TENANT_1_DOMAIN);
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/example-fastify-web-mcd/src/index.spec.ts` around lines 80 - 92, Add
a new regression test after the existing 'login from an unmapped host falls back
to the default domain' test to cover the case where the Host header does not
include a port. Create a test similar to the existing one but use a host header
value like 'unknown.localhost' (without the port number :3000) in the
fastify.inject call, and verify it still falls back to the DEFAULT_DOMAIN and
behaves consistently. This ensures the host resolver handles both host-with-port
and host-without-port variations correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/examples.yml:
- Around line 24-25: The actions/checkout action in the checkout code step is
persisting credentials unnecessarily, increasing token exposure risk. Add the
persist-credentials parameter set to false to the actions/checkout action
configuration. Since this workflow only performs read operations and does not
push or write any changes, credential persistence is not needed and should be
explicitly disabled to follow security best practices.

In `@examples/example-fastify-web-mcd/package.json`:
- Line 21: The `@auth0/auth0-fastify` dependency in package.json is pinned to an
unsafe wildcard version (*) which prevents reproducible builds and can introduce
breaking changes. Replace the wildcard version with a specific pinned version
using the caret syntax (^1.3.0) to lock the dependency to a known stable release
while allowing compatible patch updates. This ensures consistent builds across
different environments and time periods.

In `@examples/example-fastify-web-mcd/README.md`:
- Around line 19-22: The code fence for the hosts file entries (127.0.0.1
tenant-1.localhost and 127.0.0.1 tenant-2.localhost) is missing a language
identifier, which causes markdown linting to fail. Change the opening code fence
from triple backticks with no language to triple backticks followed by `text` to
properly specify the content type and satisfy markdown linting requirements.

In `@examples/example-fastify-web-mcd/src/index.ts`:
- Line 95: The reply.redirect call on line 95 embeds the raw request.url
directly into the returnTo query parameter without URL encoding, which allows
special characters like ampersands and equals signs to break the query string
syntax. Wrap the request.url value with encodeURIComponent() to properly encode
it before interpolating it into the redirect URL string.
- Around line 37-53: The environment variable assignments for
defaultAuth0Domain, AUTH0_CUSTOM_DOMAIN_1, and AUTH0_CUSTOM_DOMAIN_2 use `as
string` type assertions which only silence TypeScript but do not validate the
values at runtime. Add explicit validation checks to ensure each of these
required environment variables is actually defined before using them, and throw
an error immediately if any are missing. This prevents undefined values from
propagating into the domainsByHost map and resolveAuth0Domain function, which
would cause harder-to-debug failures later.
- Around line 38-53: The resolveAuth0Domain function performs an exact match
lookup of the host parameter against the domainsByHost map, but incoming Host
headers from proxies may omit the port or have inconsistent formatting, causing
the lookup to fail and silently fall back to the default domain. Normalize the
host parameter before the lookup against domainsByHost to handle cases where the
port may or may not be present in the Host header, ensuring consistent
resolution of the correct Auth0 custom domain for multi-tenant setups.

In `@examples/example-fastify-web-mcd/views/layout.ejs`:
- Line 45: The "Log out" link on line 45 uses unescaped EJS output syntax (<%-
... %>) when rendering locals.user.name, which creates an XSS vulnerability if
the user name contains malicious content. Replace the unescaped output syntax
<%- ... %> with the escaped output syntax <%= ... %> for the expression
containing locals.user.name to ensure user-controlled data is properly escaped
before being rendered in the HTML.

---

Nitpick comments:
In `@examples/example-fastify-web-mcd/src/index.spec.ts`:
- Around line 80-92: Add a new regression test after the existing 'login from an
unmapped host falls back to the default domain' test to cover the case where the
Host header does not include a port. Create a test similar to the existing one
but use a host header value like 'unknown.localhost' (without the port number
:3000) in the fastify.inject call, and verify it still falls back to the
DEFAULT_DOMAIN and behaves consistently. This ensures the host resolver handles
both host-with-port and host-without-port variations correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1848e135-4d7f-4603-890c-46222ea6dee5

📥 Commits

Reviewing files that changed from the base of the PR and between 3e40188 and b029041.

⛔ Files ignored due to path filters (1)
  • examples/example-fastify-web-mcd/public/img/auth0.png is excluded by !**/*.png
📒 Files selected for processing (12)
  • .github/workflows/examples.yml
  • README.md
  • examples/example-fastify-web-mcd/.env.example
  • examples/example-fastify-web-mcd/README.md
  • examples/example-fastify-web-mcd/package.json
  • examples/example-fastify-web-mcd/src/index.spec.ts
  • examples/example-fastify-web-mcd/src/index.ts
  • examples/example-fastify-web-mcd/tsconfig.json
  • examples/example-fastify-web-mcd/views/index.ejs
  • examples/example-fastify-web-mcd/views/layout.ejs
  • examples/example-fastify-web-mcd/views/private.ejs
  • examples/example-fastify-web-mcd/views/public.ejs

Comment thread .github/workflows/examples.yml
Comment thread examples/example-fastify-web-mcd/package.json
Comment thread examples/example-fastify-web-mcd/README.md Outdated
Comment thread examples/example-fastify-web-mcd/src/index.ts
Comment thread examples/example-fastify-web-mcd/src/index.ts
Comment thread examples/example-fastify-web-mcd/src/index.ts
Comment thread examples/example-fastify-web-mcd/views/layout.ejs Outdated
@frederikprijck frederikprijck force-pushed the worktree-examples branch 2 times, most recently from 645ffa9 to 0024d6f Compare July 14, 2026 08:01
Adds example-fastify-web-mcd, demonstrating MCD support in
@auth0/auth0-fastify via a per-request domain resolver. A single app
serves two hostnames (brand-a.localhost and brand-b.localhost) mapped to
different Auth0 custom domains of the same tenant, with the app base URL
inferred per request in resolver mode.

Includes a vitest spec (msw + fastify.inject) covering per-host domain
resolution and default-domain fallback, a Build and Test Examples
workflow, and a link from the root README.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant