Merge pull request #429 from the-luap/fix/cookie-secure-auto-default-427
fix(auth): default COOKIE_SECURE to 'auto' in production + first-install UX (#427)
This commit is contained in:
+22
-9
@@ -10,18 +10,31 @@ PORT=3001
|
||||
JWT_SECRET=your-very-secure-jwt-secret-at-least-32-characters-long-example123456
|
||||
|
||||
# Auth cookie Secure flag
|
||||
# unset - default: follows NODE_ENV (production=true, dev=false)
|
||||
# true - always set Secure (HTTPS-only cookies; breaks plain-HTTP access)
|
||||
# unset - default: 'auto' in production, false in dev (#427)
|
||||
# true - always set Secure (HTTPS-only cookies; breaks plain-HTTP access —
|
||||
# login appears to succeed but the browser silently drops the
|
||||
# cookie, leaving you in a redirect loop. Only set this if you
|
||||
# ALWAYS reach the site via HTTPS)
|
||||
# false - never set Secure (allows HTTP; cookies not protected on HTTPS)
|
||||
# auto - decide per request: Secure on HTTPS, not on HTTP
|
||||
# auto - decide per request: Secure on HTTPS, not on HTTP. Reads
|
||||
# req.secure from Express which respects X-Forwarded-Proto from a
|
||||
# trusted reverse proxy. This is the default and is the right
|
||||
# choice for most deployments.
|
||||
#
|
||||
# Use COOKIE_SECURE=auto if your deployment is reachable over both HTTPS
|
||||
# (via a reverse proxy like Nginx Proxy Manager, Traefik, Caddy) AND plain
|
||||
# HTTP (e.g. LAN access at http://192.168.x.x:3001). The backend reads
|
||||
# req.secure from Express, which respects the X-Forwarded-Proto header
|
||||
# when the proxy is in the trust list.
|
||||
# Why 'auto' is the default in production:
|
||||
# - On real HTTPS (reverse proxy with X-Forwarded-Proto), req.secure is
|
||||
# true → Secure flag is still emitted. No security regression vs. true.
|
||||
# - On plain HTTP (LAN access, first-time install before reverse proxy is
|
||||
# wired up), req.secure is false → Secure flag is omitted → login works
|
||||
# instead of silently looping back to /admin/login.
|
||||
#
|
||||
# Requirements for auto mode:
|
||||
# When you'd set this explicitly:
|
||||
# - COOKIE_SECURE=true → strict HTTPS-only deployments where you want
|
||||
# defense in depth against accidentally serving over HTTP.
|
||||
# - COOKIE_SECURE=false → you intentionally only ever serve over HTTP and
|
||||
# don't want the per-request check (rare).
|
||||
#
|
||||
# Requirements for 'auto' mode to detect HTTPS correctly:
|
||||
# 1. Your reverse proxy MUST send X-Forwarded-Proto: https on HTTPS
|
||||
# requests. Standard configs for NPM/Traefik/Caddy do this by default.
|
||||
# 2. The proxy must be on a trusted IP range. By default PicPeak trusts
|
||||
|
||||
@@ -7,15 +7,24 @@ const DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
|
||||
|
||||
/**
|
||||
* Cookie "Secure" flag mode:
|
||||
* - true → always set Secure (HTTPS-only)
|
||||
* - false → never set Secure (allow plain HTTP)
|
||||
* - true → always set Secure (HTTPS-only — cookie won't be sent over HTTP at all)
|
||||
* - false → never set Secure (allow plain HTTP — cookie has no in-flight protection)
|
||||
* - 'auto' → decide per-request based on req.secure (X-Forwarded-Proto
|
||||
* via Express `trust proxy`). Useful when the same deployment
|
||||
* is reachable over both HTTPS (via reverse proxy) and LAN HTTP.
|
||||
* via Express `trust proxy`). Emits Secure when actual HTTPS is
|
||||
* detected, omits it on plain HTTP. This is the right default
|
||||
* for deployments reachable via both HTTPS (reverse proxy) and
|
||||
* LAN HTTP, and for first-time installs that haven't set up a
|
||||
* reverse proxy yet.
|
||||
*
|
||||
* Default: follows NODE_ENV (production → true, dev → false) — unchanged
|
||||
* from previous behavior. Users who want the auto mode must opt in with
|
||||
* COOKIE_SECURE=auto in their .env.
|
||||
* Default:
|
||||
* - production → 'auto' (#427: previously hard `true`, which caused silent
|
||||
* login loops over HTTP because the browser drops the
|
||||
* Secure cookie. 'auto' is strictly more lenient than `true`
|
||||
* on real HTTPS — req.secure is true → Secure flag still
|
||||
* emitted — so this is not a security regression for
|
||||
* reverse-proxy deployments. Users who explicitly want the
|
||||
* HTTPS-only behaviour can still set COOKIE_SECURE=true.)
|
||||
* - dev → false (allow http://localhost in browsers without HSTS gymnastics)
|
||||
*/
|
||||
const secureCookieMode = (() => {
|
||||
const raw = typeof process.env.COOKIE_SECURE === 'string'
|
||||
@@ -24,8 +33,10 @@ const secureCookieMode = (() => {
|
||||
if (raw === 'auto') return 'auto';
|
||||
if (raw === 'true') return true;
|
||||
if (raw === 'false') return false;
|
||||
// No env var set → legacy default
|
||||
return process.env.NODE_ENV === 'production';
|
||||
// No env var set → infer from NODE_ENV. Production defaults to 'auto'
|
||||
// (per-request) rather than hard `true` so first-time HTTP installs don't
|
||||
// silently fail (#427).
|
||||
return process.env.NODE_ENV === 'production' ? 'auto' : false;
|
||||
})();
|
||||
const sameSiteDefault = process.env.COOKIE_SAMESITE || 'Lax';
|
||||
const cookieDomain = process.env.COOKIE_DOMAIN;
|
||||
|
||||
Reference in New Issue
Block a user