Home/Learn/Full-Stack Integration/Domains, Cookies and TLS in Production

Domains, Cookies and TLS in Production

Advanced
Deploying Both

The domain layout decides whether cookies work. Two subdomains of one site is a completely different situation from two unrelated hosts, and only the first lets a normal cookie through.

Overview

This is the single most common production-only failure in a two-deployable app: authentication works perfectly on localhost and fails the moment it is deployed, with no error message beyond a 401. The cause is almost always that localhost:3000 and localhost:8000 are the same site, while aicancode.org and tool-hub-api.fly.dev are not — so a SameSite=Lax cookie is simply never sent. Choosing the domain layout deliberately, before launch, avoids the whole class of problem.

Same Site or Cross Site

The distinction that decides everything, and it is not the same as same-origin.

Subdomains are same-site; fly.dev is not
// SAME-ORIGIN  scheme + host + port identical    (CORS cares)
// SAME-SITE    the registrable domain matches         (COOKIES care)

aicancode.org  and  api.aicancode.org
//   different origins -> CORS applies
//   SAME SITE          -> a SameSite=Lax cookie IS sent

aicancode.org  and  tool-hub-api.fly.dev
//   different origins AND cross-site
//   -> a Lax cookie is NEVER sent. Auth silently fails.

// The three workable layouts:
//   1. api.aicancode.org  (a CNAME to Fly)
//      same-site, Lax works, cookies are simple. Best default.
//   2. aicancode.org/api  (proxied through Next rewrites)
//      same-ORIGIN, so no CORS either. Costs an extra hop.
//   3. a genuinely different domain
//      needs SameSite=None; Secure, plus CSRF protection, and
//      browsers increasingly restrict third-party cookies anyway.

// Layout 3 is where people end up by accident, by shipping with the
// platform-provided hostname. Set up the custom domain before
// launch, not after the first login bug.

Getting the Cookie Right

The attributes for each layout, and the Domain trap.

Lax + subdomain, or None + Secure + CSRF
# Same-site (api.aicancode.org)
response.set_cookie("sid", value,
    httponly=True, secure=True, samesite="lax",
    domain=".aicancode.org",      # so both the app and the API see it
    path="/", max_age=86400)

# Cross-site (a different registrable domain)
    samesite="none", secure=True   # None REQUIRES Secure
# and now you need CSRF tokens, because the cookie is sent on
# cross-site requests by design.

# The Domain trap: setting domain=".aicancode.org" shares the cookie
# with EVERY subdomain, including any you do not control — a staging
# app, a docs site, a customer subdomain. Omit Domain to scope it to
# the exact host unless sharing is the intent.

# Secure means the cookie is not sent over http at all, which is why
# it "works locally" (http is exempt for localhost) and disappears in
# production if TLS terminates somewhere unexpected.

# Behind a proxy, tell the app it is behind one, or it builds http://
# redirect URLs and sets insecure cookies:
app.add_middleware(ProxyHeadersMiddleware, trusted_hosts="*")
# (X-Forwarded-Proto / X-Forwarded-For)

TLS, Redirects and Headers

The rest of the production edge configuration.

One canonical host, HSTS, and connect-src
# Certificates: automatic on both platforms
flyctl certs add api.aicancode.org      # then add the CNAME/A records
# Vercel provisions for the apex and www automatically.

# Pick ONE canonical host and redirect the other, or you split
# sessions, cookies, analytics and SEO across two:
#   www.aicancode.org -> aicancode.org   (301)

# Security headers, once, at the edge:
Strict-Transport-Security: max-age=63072000; includeSubDomains
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy: default-src 'self'; connect-src 'self' https://api.aicancode.org
# connect-src must include the API origin, or fetch is blocked by
# your own CSP — a self-inflicted failure that looks like CORS.

# A checklist for the domain change itself, because each item has
# been a production incident somewhere:
#   [ ] API reachable on the new host over HTTPS
#   [ ] ALLOWED_ORIGINS updated on the backend
#   [ ] NEXT_PUBLIC_API_URL updated and the frontend REBUILT
#   [ ] cookie domain and SameSite reviewed
#   [ ] OAuth redirect URIs updated at every provider
#   [ ] webhook URLs updated at every provider
#   [ ] old host redirects rather than 404s

Key Points to Remember

  • 1Same-origin governs CORS; same-site governs cookies — two localhost ports are same-site, two real domains usually are not
  • 2Putting the API on a subdomain of the app keeps cookies same-site and Lax working, which is the simplest layout
  • 3SameSite=None requires Secure and brings CSRF protection back into scope
  • 4A Domain attribute shares the cookie with every subdomain, including ones you may not control
  • 5A CSP connect-src that omits the API origin blocks your own fetches and looks exactly like a CORS failure

Interview Questions

Sign in to ask Aria
1

Why does cookie authentication often break only after deploying to production?

Hard
2

What is the difference between same-origin and same-site?

Hard
3

What has to change when you move an API from fly.dev to api.yourdomain.com?

Medium

Ask Aria about Domains, Cookies and TLS in Production

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…