823
@ship_it_soren ·

Session cookie works on localhost but vanishes the moment I deploy Help

Login returns 200 and the Set-Cookie header is definitely in the response, I can see it in the network tab. The very next request goes out with no cookie at all and the API returns 401. On my machine everything works, because the dev server proxies /api to the backend. In production the frontend is on one host and the API is on another. I've been staring at this for two days.

9 answers Share
Report

Answering anonymously — a moderator will review it first.

  • @hotswap_hana · 2mo ago

    Fastest way to confirm the diagnosis before you change any code: open the app, then look in devtools under Application, Cookies, and select the API's origin rather than the page's origin. If the cookie is sitting there quite happily but not being attached, it's a SameSite or credentials problem. If it isn't there at all, it's a Secure or Domain problem. Two very different afternoons.

    341
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @midnight_pager · 2mo ago · 3 replies

    You've described the cause in your own post without noticing. The dev proxy makes everything same-origin, so the cookie is first-party and the browser is relaxed about it. In production the frontend and the API are different sites, so that cookie is now a third-party cookie and the default SameSite=Lax means it is never attached to a cross-site XHR.

    Minimum to make it work as-is:

    • SameSite=None; Secure on the cookie, and Secure means real HTTPS on both ends
    • credentials: 'include' on every fetch, it is not the default
    • Access-Control-Allow-Credentials: true on the API
    • Access-Control-Allow-Origin set to your exact frontend origin, because a wildcard is ignored when credentials are involved

    Miss any one of those four and you get exactly your symptom, header present, cookie never sent.

    The better fix, if you can, is to stop it being cross-site at all. Put the API behind the same hostname on a path, example.com/api, via a reverse proxy or your host's rewrites. Then everything is first-party, none of the above applies, and Safari's tracking prevention stops being your problem in six months.

    612
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @midnight_pager · 2mo ago

      Everyone learns that one the same way. Also do the reverse proxy thing when you have a spare afternoon, it will save you the third-party cookie deprecation conversation entirely.

      174
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
    • @ship_it_soren · 2mo ago

      credentials: 'include' was missing. Added the other three too. Working. I had convinced myself the browser sent cookies automatically because that's what it does everywhere else.

      231
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @boring_portfolio · 2mo ago

    One more that bites people once: Secure cookies require HTTPS, and if your staging environment terminates TLS at a load balancer and talks plain HTTP to the app, some frameworks decide the request is insecure and silently refuse to set the cookie. Look for a trust proxy setting.

    196
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @cold_storage_kim · 2mo ago · 2 replies

    Check the Domain attribute too. If your backend is setting Domain=api.example.com explicitly and your app runs on app.example.com, the browser will store it but never send it to the app's requests. Either drop the attribute so it defaults to the host that set it, or set it to the parent example.com.

    287
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @stack_trace_sy · 2mo ago

      And check whether whatever sits in front of your API is rewriting or dropping Set-Cookie. Some CDN caching configs strip it on responses they think are cacheable and it's invisible from the browser side.

      118
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @blistermaps · 2mo ago · 2 replies

    Just use localStorage for the token, cookies are a nightmare with CORS.

    21
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @rowan_ellery · 2mo ago

      That trades a configuration problem for a security one. A token in localStorage is readable by any script that ends up on your page, which is the whole reason HttpOnly cookies exist.

      15
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report