Server-side fixes

Every failure the last few pages walked through — a missing Access-Control-Allow-Origin, a wildcard that can't satisfy a credentialed request, a header the preflight never approved — is fixed the same way: on the server, by opting in deliberately rather than either refusing outright or opening the door to everyone. CORS is enforced by the browser, but it is answered by your backend, and the fix is always a few response headers, sent correctly. This page is that reference: the real configuration this project runs in production, plus the same pattern translated into a few common server stacks.

How this project does it

The two functions below are not a hypothetical — they are the actual code this site's own API runs, live, for every allowlist-gated route it serves (the report channel and the demo cookie routes). Both follow the same rule that matters most for correctness: the emitted Access-Control-Allow-Origin is always the allowlisted entry itself, never a copy of whatever Origin the request happened to carry — so a non-allowlisted origin can never get its own value reflected back at it.

The baseline grant

applyAllowlistCors emits an exact-match Access-Control-Allow-Origin only when the request's Origin is in the allowlist, plus Vary: Origin so a shared cache never serves one origin's grant to another. This is the general-purpose fix for a missing or overly-loose ACAO: allowlist, echo the matched entry, done.

/**
 * Apply the site's fixed, correct CORS grant: emit
 * `Access-Control-Allow-Origin` (allowlist-gated to the given origins) plus
 * `Vary: Origin`. Used by any route whose CORS configuration is fixed and
 * correct, never scenario-controlled (SPEC §2.5) — the report channel, and
 * (with credentials added below) the cookie routes.
 *
 * The grant is emitted only when the request carries an `Origin` header that
 * exactly matches one of `allowedOrigins`. The emitted value is that
 * allowlisted entry itself, never a copy of the received header value — so a
 * third-party origin can never obtain its own string back as ACAO (SPEC §2.7).
 * `Vary: Origin` rides along so a shared cache never serves one origin's grant
 * to another (the CDN-poisoning trap, page 8).
 */
export function applyAllowlistCors(c: Context, allowedOrigins: Set<string>): void {
  const origin = c.req.header("Origin") ?? null;
  if (origin !== null && allowedOrigins.has(origin)) {
    c.header("Access-Control-Allow-Origin", origin);
    c.header("Vary", "Origin");
  }
}

Adding credentials support

A credentialed cross-origin fetch (cookies, or an Authorization header) needs one header more: Access-Control-Allow-Credentials: true. It can never be paired with a wildcard ACAO — the Fetch spec forbids that combination outright — so applyCredentialsCors simply layers it on top of the same exact-origin grant above, gated by the same allowlist. This project uses it for exactly the routes that set or clear cookies a credentialed fetch has to be allowed to store; the cookie semantics themselves (SameSite, Domain) are governed by RFC 6265bis, a separate concern from the CORS grant that lets the response be read at all.

/**
 * Apply the fixed CORS grant plus `Access-Control-Allow-Credentials: true` —
 * required whenever a route's response sets or clears a cookie a credentialed
 * cross-origin fetch must be allowed to store, or whose body a credentialed
 * cross-origin fetch must be allowed to read (SPEC §2.5's cookie-route CORS
 * config). Never used by `/demo/echo` or `/report/*` — only the
 * `/demo/cookie/*` routes.
 *
 * `Access-Control-Allow-Credentials: true` is emitted only when the origin is
 * allowlisted, alongside the exact-match ACAO from `applyAllowlistCors`. The
 * Fetch spec forbids `Access-Control-Allow-Origin: *` together with
 * credentials — the two functions together always emit the exact origin, never
 * the wildcard, so this combination is always valid.
 */
export function applyCredentialsCors(c: Context, allowedOrigins: Set<string>): void {
  applyAllowlistCors(c, allowedOrigins);
  const origin = c.req.header("Origin") ?? null;
  if (origin !== null && allowedOrigins.has(origin)) {
    c.header("Access-Control-Allow-Credentials", "true");
  }
}

The same pattern in other stacks

This project's own fixes only cover the routes it actually runs — a real reader is far more likely to be reaching for nginx, Express, or a Python framework. The three snippets below are illustrative: sketches of the same allowlist-echo pattern in each stack's own idiom, not tested or shipped code from this repository. Each holds the line on the disciplines above — exact-origin echo, Vary: Origin, and a correctly answered preflight — plus one more that's easy to get wrong even when the origin handling is right: Access-Control-Allow-Headers is answered with a fixed, explicit list, never by echoing the browser's own Access-Control-Request-Headers value straight back. Reflecting whatever the visitor asked for is a one-line shortcut that looks harmless — and it's the exact mistake two of three early candidate builds of this project's own demo API made, caught by review before either was merged.

nginx Illustrative

Illustrative nginx configuration — nginx has no built-in CORS handling, so both the allowlist match (via a map block) and the preflight's OPTIONS short-circuit have to be written out explicitly.

# A fixed allowlist, resolved via a map block — never a bare "*",
# and never $http_origin echoed back unconditionally.
map $http_origin $cors_origin {
    default                      "";
    "https://app.example.com"    $http_origin;
    "https://admin.example.com"  $http_origin;
}

server {
    location /api/ {
        # Only ever the allowlisted value the map just resolved — an
        # unrecognised Origin resolves to "", so add_header sends nothing.
        add_header Access-Control-Allow-Origin $cors_origin always;
        add_header Vary Origin always;

        if ($request_method = OPTIONS) {
            add_header Access-Control-Allow-Origin $cors_origin always;
            add_header Vary Origin always;
            add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE" always;
            # A fixed, explicit list — never the raw
            # Access-Control-Request-Headers value echoed back.
            add_header Access-Control-Allow-Headers "Content-Type, X-Requested-With" always;
            add_header Access-Control-Max-Age 600 always;
            add_header Content-Length 0;
            return 204;
        }

        proxy_pass http://upstream;
    }
}

Express Illustrative

Illustrative Express middleware — the same allowlist-echo pattern as one small piece of request-handling middleware, run before every route.

const ALLOWED_ORIGINS = new Set([
  "https://app.example.com",
  "https://admin.example.com",
]);

// A fixed, explicit allowlist — never the raw
// Access-Control-Request-Headers value echoed back.
const ALLOWED_HEADERS = "Content-Type, X-Requested-With";

app.use((req, res, next) => {
  const origin = req.headers.origin;
  if (origin && ALLOWED_ORIGINS.has(origin)) {
    res.setHeader("Access-Control-Allow-Origin", origin);
    res.setHeader("Vary", "Origin");
  }

  if (req.method === "OPTIONS") {
    res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
    res.setHeader("Access-Control-Allow-Headers", ALLOWED_HEADERS);
    res.setHeader("Access-Control-Max-Age", "600");
    return res.status(204).end();
  }

  next();
});

Flask Illustrative

Illustrative Flask (Python) — an after_request hook for the origin grant on every response, plus one explicit OPTIONS route for the preflight.

ALLOWED_ORIGINS = {"https://app.example.com", "https://admin.example.com"}
# A fixed, explicit allowlist — never the raw
# Access-Control-Request-Headers value echoed back.
ALLOWED_HEADERS = "Content-Type, X-Requested-With"


@app.after_request
def apply_cors(response):
    origin = request.headers.get("Origin")
    if origin in ALLOWED_ORIGINS:
        response.headers["Access-Control-Allow-Origin"] = origin
        response.headers["Vary"] = "Origin"
    return response


@app.route("/api/<path:_unused>", methods=["OPTIONS"])
def preflight(_unused):
    response = current_app.make_default_options_response()
    response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE"
    response.headers["Access-Control-Allow-Headers"] = ALLOWED_HEADERS
    response.headers["Access-Control-Max-Age"] = "600"
    return response