Docs / Verifying requests

Verifying requests

How your origin verifies the edge's signed X-Custom-Domain-Assertion header and selects the workspace from it, with the Python SDK, a Node.js example and the exact algorithm for any language.

Every request the edge forwards to your origin carries:

X-Custom-Domain-Assertion: v1.<key id>.<base64url payload>.<base64url HMAC-SHA256>

It says which application, domain and workspace the edge routed the request for. Select the workspace from the verified assertion, never from the Host header: a hostname alone doesn’t prove which workspace may be served.

You need two values, both on the Origin page of your application: your application id, and your assertion key (its id, starting with app_, and its secret).

Python (FastAPI, Starlette, any ASGI app)

import os
from custom_domain import CustomDomainMiddleware

app.add_middleware(
    CustomDomainMiddleware,
    keys={"app_…": os.environ["CUSTOM_DOMAIN_ASSERTION_SECRET"]},
    application_id=os.environ["CUSTOM_DOMAIN_APPLICATION_ID"],
    workspace_lookup=load_workspace,   # reference -> workspace, or None
    on_missing="reject",               # "passthrough" if this app also serves its own domain
)

@app.get("/")
def home(request):
    assertion = request.state.custom_domain
    workspace = load_workspace(assertion.reference)

Any other Python framework:

from custom_domain import WorkspaceResolver, AssertionInvalid

resolver = WorkspaceResolver(keys={"app_…": SECRET}, application_id=APPLICATION_ID)
try:
    assertion = resolver.resolve(request.headers, request.host)
except AssertionInvalid as exc:
    return forbidden(exc.code)  # missing, expired, bad_signature, wrong_application, ...
workspace = load_workspace(assertion.reference)

Node.js

import crypto from "node:crypto";

// keys: { "app_…": "<secret>" }; keep the previous key here during a rotation.
export function verifyAssertion(token, keys, { applicationId, host, skew = 30 }) {
  const parts = (token || "").split(".");
  if (parts.length !== 4 || parts[0] !== "v1") throw new Error("malformed");
  const [, keyId, payload, signature] = parts;
  const secret = keys[keyId];
  if (!secret) throw new Error("unknown_key");
  const expected = crypto.createHmac("sha256", secret).update(`v1.${keyId}.${payload}`).digest();
  const given = Buffer.from(signature, "base64url");
  if (given.length !== expected.length || !crypto.timingSafeEqual(given, expected)) {
    throw new Error("bad_signature");
  }
  const data = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
  const now = Math.floor(Date.now() / 1000);
  if (data.iat > now + skew) throw new Error("not_yet_valid");
  if (data.exp + skew < now) throw new Error("expired");
  if (data.app !== applicationId) throw new Error("wrong_application");
  if (data.host !== host.toLowerCase().split(":")[0]) throw new Error("wrong_hostname");
  return { reference: data.ref, domainId: data.dom, hostname: data.host, requestId: data.rid };
}

The algorithm, for any language

  1. Read X-Custom-Domain-Assertion. Reject the request if it is missing.
  2. Split it on .; require exactly four parts, the first being v1.
  3. Look up the secret by the key id (the second part). Reject unknown ids.
  4. Compute HMAC-SHA256 with that secret over v1.<key id>.<payload> and compare it, in constant time, with the base64url-decoded fourth part.
  5. Decode the payload (base64url, then JSON). Reject it if exp plus 30 seconds is in the past, or iat is more than 30 seconds in the future.
  6. Require app to equal your application id, and host to equal the request’s hostname.
  7. Only then use ref to select the workspace.
Payload field Meaning
app Your application id.
dom The domain id.
ref The workspace reference you registered the domain with.
host The canonical hostname.
iat, exp Issued and expires, Unix seconds (60 seconds apart).
rid The edge’s request id.

The workspace endpoint

Before a domain goes live, the service calls your origin through the edge:

GET /.well-known/custom-domain-workspace

Verify the assertion as above, and answer 200 with the workspace you selected:

{"reference": "ws_8f3a1c", "application": "<your application id>"}

Answer 404 if the workspace doesn’t exist. The Python middleware does this for you. A workspace_mismatch here means your origin isn’t selecting the workspace from the assertion.

Rotating the key

On the Origin page, Rotate the key issues a new one that starts signing 24 hours later. Add the new key id and secret to your keyring next to the current one; after the switch, remove the old one.

Keep the origin behind the edge

An assertion is valid for 60 seconds and bound to one hostname and workspace. Even so, don’t let the custom-domain path of your origin be reached directly: allow only requests with a valid assertion there, or restrict it to the edge at the network level.