Add SESSION_COOKIE_DOMAIN so /admin SSR sees the session cookie

Production splits web (descrybe.io) and API (api.descrybe.io). The session
cookie was host-only for api.descrybe.io, so the browser never sent it to
the web host. The /admin SvelteKit SSR gate (fetchMeStaff in
+layout.server.ts) forwards the incoming cookie header to /api/auth/me —
with no cookie to forward it always got 401 and bounced every successful
login back to /login?next=/admin (login POST 200, /me 200 from the
browser, /me 401 from the web server).

New SESSION_COOKIE_DOMAIN env (default empty = host-only, local dev
unchanged) sets the session cookie Domain attribute; set it to the parent
domain (descrybe.io) in production so both hosts receive the cookie.
Leading dot is normalized away. CSRF needs no change — it already seeds
cross-origin via the X-CSRF-Token response header.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-18 00:31:58 +02:00
co-authored by Claude Fable 5
parent b595398389
commit 0eef202f56
6 changed files with 35 additions and 5 deletions
+10 -1
View File
@@ -2,6 +2,7 @@ package auth
import (
"net/http"
"strings"
"time"
"github.com/alexedwards/scs/pgxstore"
@@ -9,7 +10,14 @@ import (
"github.com/jackc/pgx/v5/pgxpool"
)
func NewSessionManager(pool *pgxpool.Pool, cookieName string, secure bool, idleHours int) *scs.SessionManager {
// NewSessionManager builds the scs session manager. cookieDomain is the session
// cookie Domain attribute: empty keeps a host-only cookie (localhost / same-host
// deploys). When the web app and API live on sibling hosts of one parent domain
// (descrybe.io + api.descrybe.io), set SESSION_COOKIE_DOMAIN=descrybe.io so the
// browser also sends the session cookie to the web host — SvelteKit SSR gates
// (/admin +layout.server.ts fetchMeStaff) forward it to /api/auth/me and would
// otherwise always see 401.
func NewSessionManager(pool *pgxpool.Pool, cookieName, cookieDomain string, secure bool, idleHours int) *scs.SessionManager {
sm := scs.New()
sm.Store = pgxstore.New(pool)
sm.Lifetime = 7 * 24 * time.Hour
@@ -18,6 +26,7 @@ func NewSessionManager(pool *pgxpool.Pool, cookieName string, secure bool, idleH
}
sm.IdleTimeout = time.Duration(idleHours) * time.Hour
sm.Cookie.Name = cookieName
sm.Cookie.Domain = strings.TrimPrefix(strings.TrimSpace(cookieDomain), ".")
sm.Cookie.HttpOnly = true
sm.Cookie.Secure = secure
sm.Cookie.SameSite = http.SameSiteLaxMode