Optionaloptions: { trustForwardedHost?: boolean }import express from 'express';
import { resolveHost } from '@adcp/sdk/server';
const routersByHost = new Map<string, express.Router>();
function hostDispatch(req, res, next) {
const host = resolveHost(req, { trustForwardedHost: true });
const router = routersByHost.get(host);
if (!router) return res.status(404).end();
return router(req, res, next);
}
Resolve the canonical host the request arrived on for multi-host routing and per-host PRM / audience advertisement. Same logic
serve()uses internally — exported so callers writing their own host-dispatch middleware (e.g., behindcreateExpressAdapter) can matchserve()'s semantics exactly instead of re-implementing them.When
options.trustForwardedHost: true, consults in order:X-Forwarded-Host(most common — Fly, Cloud Run, most ALBs).Forwarded: host=...(spec-standard, less common).Host.When
trustForwardedHost: false(default), onlyHostis read — an attacker-controlled forwarded header can't flip the advertised OAuthresourceURL.Proxy behavior matters when you opt in. The helper trusts the FIRST entry in a forwarded chain. That's safe when your proxy OVERWRITES the header on ingress (rewriting the original value from the client). It's UNSAFE when your proxy APPENDS — the attacker gets to pick the first entry. Fly, Cloud Run, and GCP HTTPS LBs overwrite; AWS ALB and nginx (by default) append. Verify your proxy's behavior before enabling
trustForwardedHost.Normalizes to lowercase and preserves port. Returns empty string when no usable header is present (HTTP/1.1 requires
Host, so this is unusual — callers can branch on it to fail closed).