CORS gets weird the moment DNS enters the room.

A lot of teams assume that if they point api.example.com to a vendor with a CNAME, the browser will somehow treat that endpoint as “same-origin enough” with app.example.com. It won’t. Browsers care about origin, not your DNS intent. A custom subdomain can make things cleaner for cookies, branding, and certificate management, but it does not magically bypass CORS.

That distinction matters when you’re choosing between:

  1. calling a vendor API on its vendor hostname, or
  2. putting that vendor behind your own custom DNS name with a CNAME.

I’ve seen teams burn days on this because the setup looks same-site, but the browser still sees a cross-origin request and enforces CORS exactly the same way.

The core rule: CNAME does not change origin semantics

If your frontend runs on:

https://app.example.com

and your API is on:

https://api.example.com

that is still cross-origin because the hostnames differ.

Even if api.example.com is just a CNAME to:

customer-id.vendor-api.com

the browser origin is still https://api.example.com, and requests from app.example.com to api.example.com still need proper CORS headers.

Same site is not the same as same origin.

  • Same-origin requires same scheme, host, and port
  • Same-site is broader and mostly matters for cookies and CSRF behavior

That’s the first trap.

Two common patterns

Pattern 1: Use the vendor hostname directly

Your frontend calls:

fetch("https://customer-id.vendor-api.com/data", {
  credentials: "include"
});

Pros

  • Easiest to set up
  • Fewer DNS and certificate moving parts
  • Clear separation between your app and the provider
  • Vendor docs usually assume this model

Cons

  • Your API hostname looks third-party
  • Cookie behavior can get ugly
  • You depend entirely on the vendor’s CORS configuration flexibility
  • Harder to swap providers later without touching frontend code
  • Some enterprise environments dislike obvious third-party API domains

If the vendor only supports Access-Control-Allow-Origin: *, that may be fine for public unauthenticated endpoints, but it breaks down fast when credentials are involved.

Browsers reject this combination:

Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

That is invalid for credentialed cross-origin requests.

A real-world example of a public API using wildcard CORS is GitHub’s API. api.github.com returns:

access-control-allow-origin: *
access-control-expose-headers: ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset

That’s a good fit for broad public access. It’s not a model for cookie-authenticated APIs.

Pattern 2: Put the vendor behind your custom DNS with a CNAME

Your frontend calls:

fetch("https://api.example.com/data", {
  credentials: "include"
});

And DNS says:

api.example.com. IN CNAME customer-id.vendor-api.com.

This is the setup people often choose for branding and cookie alignment.

Pros

  • Cleaner public API hostname
  • Easier future migration between providers
  • Better fit for first-party cookie strategies
  • Can reduce perceived third-party tracking risk
  • Gives you a more stable interface for clients and SDKs

Cons

  • Still requires CORS if frontend and API are on different origins
  • DNS, TLS, and vendor onboarding are more complex
  • Misconfigured vendors can accidentally allow hostile origins
  • Debugging gets harder because ownership is split across DNS, CDN, app, and vendor
  • Some people on the team will wrongly assume CORS is no longer needed

That last point is common. I’ve had to explain more than once that app.example.com calling api.example.com is still cross-origin. Better than vendor-api.com from a cookie perspective? Often yes. Free pass on CORS? No.

What actually changes with a custom CNAME

If you’re using cookies for auth, a custom subdomain can help because the API is now under your site umbrella.

For example:

  • frontend: app.example.com
  • API: api.example.com

A cookie like this may be workable:

Set-Cookie: session=abc123; Domain=.example.com; Path=/; Secure; HttpOnly; SameSite=Lax

That can support first-party flows better than a vendor-owned hostname. But cookie policy is a separate layer from CORS. You need both to line up.

2. Your CORS policy can be tighter

With a custom hostname, you can usually define exactly which frontend origins are allowed.

Good:

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Vary: Origin

Bad:

Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

Also bad:

Access-Control-Allow-Origin: https://app.example.com, https://admin.example.com

Browsers don’t accept a comma-separated list in Access-Control-Allow-Origin. You must echo one allowed origin or send a single explicit origin.

Example server logic in Node/Express:

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

app.use((req, res, next) => {
  const origin = req.headers.origin;

  if (origin && allowedOrigins.has(origin)) {
    res.setHeader("Access-Control-Allow-Origin", origin);
    res.setHeader("Access-Control-Allow-Credentials", "true");
    res.setHeader("Vary", "Origin");
    res.setHeader(
      "Access-Control-Allow-Headers",
      "Content-Type, Authorization"
    );
    res.setHeader(
      "Access-Control-Allow-Methods",
      "GET, POST, PUT, DELETE, OPTIONS"
    );
  }

  if (req.method === "OPTIONS") {
    return res.sendStatus(204);
  }

  next();
});

If you’re reflecting the Origin header dynamically, validate it strictly. I’ve seen sloppy suffix matching like this:

if (origin.endsWith(".example.com")) {
  res.setHeader("Access-Control-Allow-Origin", origin);
}

That’s risky if your trust model around subdomains is weak, or if dangling DNS / subdomain takeover is possible. A dead CNAME can turn into a very real security bug.

The big security concern: dangling CNAMEs

This is where CORS and DNS collide in a nasty way.

Imagine you have:

api-old.example.com. IN CNAME old-project.vendorcdn.com.

You stop using the vendor but forget to remove the DNS record. If an attacker can claim old-project.vendorcdn.com on that service, they may now control content served under api-old.example.com.

If your CORS policy broadly trusts https://*.example.com, or your backend reflects trusted subdomains too loosely, that forgotten CNAME can become an attack path.

I’m opinionated on this: wildcard trust for subdomains is usually lazy and dangerous.

Avoid policies like:

if (/\.example\.com$/.test(origin)) {
  res.setHeader("Access-Control-Allow-Origin", origin);
}

Safer:

  • maintain an explicit allowlist
  • audit DNS regularly
  • remove unused CNAMEs fast
  • verify subdomain ownership before trusting it in CORS

Preflight behavior does not get easier

A custom CNAME doesn’t eliminate preflight requests.

If your frontend sends:

  • Authorization
  • Content-Type: application/json
  • non-simple methods like PUT, PATCH, DELETE

the browser will still do an OPTIONS preflight.

Example:

OPTIONS /data HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type

Response:

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 600
Vary: Origin

That’s true whether the API is on a vendor hostname or your CNAME.

Comparison: direct vendor host vs custom CNAME

Direct vendor hostname

Best when:

  • the API is public or token-based
  • you don’t need first-party cookies
  • you want low operational overhead
  • vendor CORS support is already good

Watch out for:

  • poor credential support
  • ugly migration path later
  • enterprise blockers around third-party domains

Custom DNS + CNAME

Best when:

  • you want a stable first-party API hostname
  • you care about first-party cookie behavior
  • you may change providers later
  • you need tighter branding and trust boundaries

Watch out for:

  • assuming CORS disappears
  • dangling CNAME takeover risk
  • vendor limitations on TLS and header control
  • extra operational complexity across DNS, certs, and support teams

My recommendation

If the API is public and stateless, use the vendor hostname directly unless you have a strong product reason not to. It’s simpler, and simpler usually wins.

If auth relies on cookies, or you want a long-lived first-party API surface, a custom DNS name with CNAME is usually the better design. Just go into it with clear eyes: this is a DNS and operational abstraction, not a CORS shortcut.

The safe baseline I’d use:

  • explicit CORS allowlist
  • Vary: Origin
  • no wildcard origin with credentials
  • regular dangling DNS audits
  • no broad trust for *.example.com
  • tight cookie scope and SameSite review
  • standard security headers on API responses where relevant

If you’re also reviewing adjacent header policy, especially around browser-side hardening, the CSP docs at csp-guide.com are useful. Different problem space than CORS, but teams often need both cleaned up at the same time.

The short version: CNAME changes routing and ownership appearance. CORS still follows the browser’s origin model. If you remember that one rule, you’ll avoid most of the bad decisions people make here.