Why AI-built apps land in this exact configuration
AI scaffolding tools love the split-origin shape. A prompt like "build a Vue frontend with a separate API" produces a frontend project and an API project that run as two services, often on two ports locally and two domains in production. That is a clean architecture, and it is also the precise condition that makes cross-origin cookies fragile.
The reason it slips through is that localhost hides the problem. On a single machine, frontend on localhost:3000 and API on localhost:8000 are treated leniently by browsers, and a cookie set with default SameSite=Lax often still flows during development because the request is not treated as a true third-party context. The generated code runs, login works, and the build looks finished. The cookie attributes that matter only matter once the two origins are genuinely different registrable domains over HTTPS.
This is the same family of problem as a misconfigured cross-origin API; the CORS headers and the cookie attributes have to agree, and AI scaffolds tend to get one without the other. It also overlaps with general cookie-flag mistakes on the session cookie itself.
- The generated default is frequently SameSite=Lax, or no SameSite attribute at all, which the browser treats as Lax. Lax cookies are not sent on cross-site requests like a fetch from one origin to another.
- The Secure requirement is invisible in dev because localhost is exempt from the Secure rule, so a cookie without Secure still gets stored. In production over HTTPS, a SameSite=None cookie without Secure is rejected outright by the browser.
- Fast deploy means the public URL exists before anyone tested the real cross-origin, cross-domain path. The first person to hit the production login may be a real user.
What to check on your own app before launch
You can confirm all of this against your own app with the browser DevTools, no attack and no third-party target. Do it on the production origins, not just localhost, because localhost is exactly where the bug hides.
Inspect the Set-Cookie response. Log in on your deployed app, open DevTools, go to the Network tab, and click the login or session request. Look at the response headers for Set-Cookie. The safe result for a split-origin app is a cookie with both SameSite=None and Secure present, for example Set-Cookie: session=...; SameSite=None; Secure; HttpOnly. The suspicious results are: SameSite=None present but Secure missing, no SameSite attribute at all on a cookie meant for cross-origin use, or SameSite=Lax or Strict on a cookie that needs to travel cross-origin.
Confirm the cookie is actually stored and sent. In DevTools, open Application, then Storage, then Cookies, and check the cookie is present with the SameSite column reading None and the Secure column checked. Then trigger an authenticated API call from the frontend and, in the Network tab, look at that cross-origin request's Cookie request header. The safe result is the session cookie appears in the outgoing Cookie header. The suspicious result is the cookie is in storage but absent from the cross-origin request, which means the browser is declining to attach it.
- Watch the browser console while logging in. Browsers log a warning when they reject a SameSite=None cookie that lacks Secure, and when they block a cookie for SameSite reasons. A console warning about a rejected or blocked cookie is a direct signal.
- Check the request mode. For the cookie to be sent cross-origin, the frontend fetch must use credentials: include, and the API's CORS response must set Access-Control-Allow-Credentials: true with a specific Access-Control-Allow-Origin, never the wildcard. If Allow-Origin is the * wildcard while credentials are included, the browser blocks the response; that is an authorization-adjacent CORS failure, separate from the cookie attributes.
- Test the real production origins. A check that only passes on localhost proves nothing about the dev-to-prod promotion, which is where this fails silently.
How to fix it, and what the fix does not cover
The fix is to set the session cookie correctly for the cross-origin case and make the CORS contract match. None of this is hard once you know it is required.
Set the session cookie with SameSite=None and Secure together, and serve everything over HTTPS. SameSite=None without Secure is rejected by the browser, so the two are a pair, not a choice. Keep HttpOnly on the session cookie so client-side script cannot read it. On the API side, set Access-Control-Allow-Credentials: true and reflect the exact frontend origin in Access-Control-Allow-Origin rather than the wildcard, because credentialed requests are incompatible with the wildcard. On the frontend, send the request with credentials: include so the browser attaches the cookie.
There is a sturdier alternative worth knowing: if you can put the frontend and API under the same registrable domain (frontend at app.example.com and API at api.example.com, both under example.com), the request is same-site, and a SameSite=Lax cookie scoped to the parent domain can work without SameSite=None at all. That reduces the cross-site exposure surface. It is an architecture choice, not a one-line flag.
- What the fix does not cover: making the cookie cross-origin-capable does nothing for what the cookie protects. SameSite and Secure govern when the cookie is sent; they do not authenticate the user, validate the session server-side, or stop CSRF on their own.
- SameSite=None deliberately allows the cookie in cross-site contexts, which removes the partial CSRF protection that Lax provides. If you move to None, you need explicit CSRF defenses (such as anti-CSRF tokens or origin checks) rather than relying on SameSite.
- Secure ensures the cookie only travels over HTTPS. It does not encrypt the cookie's contents or make the session itself harder to steal through other paths such as cross-site scripting.
SameSite, Secure, and HttpOnly control how the browser handles the cookie. They are transport and delivery rules, not an authorization model. A correctly attached session cookie still relies on your server to validate the session and to authorize what that user is allowed to do. Distinguish the two: getting the cookie delivered is necessary, and separately your API must check that this authenticated caller is permitted to perform the action.
Where VibeCodeGuard fits, and what it does not prove
VibeCodeGuard's Launch Check looks at the public surface of your app, which includes the Set-Cookie headers your deployed app returns. It can flag a cookie sent with SameSite=None but missing the Secure attribute, and it can flag a credentialed cross-origin route where SameSite is absent on the session cookie. Those are exactly the patterns that fail silently between development and production, and seeing them in a scan is a clear signal to fix the cookie before you share the URL.
What the scan does not do is exercise your full login and cross-origin request flow as a real browser session, or read your server-side session logic. It inspects the headers visible on the public surface; it does not prove that your authentication is sound or that your authorization checks are correct.
Next step: deploy to your real production origins, log in once with DevTools open, and confirm the session cookie shows SameSite=None and Secure and appears in the Cookie header of the cross-origin API request. Run a focused Launch Check against the public URL before sharing it, so a missing Secure flag shows up in your scan and not in a user's broken login.
A clean Launch Check on cookie attributes does not prove your cross-origin auth works end to end. The scan reads the Set-Cookie header from the outside; it does not log in, follow the credentialed request, or verify that the API actually honors the session. Treat a flagged SameSite or Secure issue as a launch blocker, and treat a clean result as one signal among several, not a guarantee that login holds in production.
> launch check
Scan the public surface before launch.
Get severity, evidence, and practical fix guidance for the checks VibeCodeGuard can run from the outside.