feat(auth): explain the plain-HTTP secure-cookie gotcha on login

When the server issues a Secure session cookie but the request arrived over
plain HTTP (the common LAN install over http://ip:3000), the browser drops
the cookie and the next request dead-ends on a bare "Access token required" —
the top source of avoidable install issues. The login response now flags this
exact case and the login page shows a localized box explaining the fix (use
HTTPS, or set COOKIE_SECURE=false) with a link to the Troubleshooting guide.
It only triggers in the real failure case, never for correct HTTPS setups.
This commit is contained in:
Maurice
2026-06-29 18:24:49 +02:00
committed by Maurice
parent e91f592f22
commit 3701ab6cad
25 changed files with 111 additions and 3 deletions
@@ -4,6 +4,7 @@ import { AuthService } from './auth.service';
import { RateLimitService } from './rate-limit.service';
import { OptionalJwtGuard } from './optional-jwt.guard';
import { writeAudit, getClientIp } from '../../services/auditLog';
import { willDropSecureCookie } from '../../services/cookie';
import type { User } from '../../types';
const WINDOW = 15 * 60 * 1000;
@@ -88,7 +89,13 @@ export class AuthPublicController {
return { mfa_required: true, mfa_token: result.mfa_token };
}
this.auth.setAuthCookie(res, result.token!, req, result.remember);
return { token: result.token, user: result.user };
return {
token: result.token,
user: result.user,
// Surfaced so the client can explain the plain-HTTP cookie gotcha instead
// of the user hitting a bare "Access token required" on the next request.
...(willDropSecureCookie(req) ? { insecureCookie: true } : {}),
};
}
@Post('forgot-password')
+16
View File
@@ -54,6 +54,22 @@ function buildOptions(clear: boolean, secure: boolean, remember?: RememberOption
};
}
/**
* True when we are about to set a `Secure` session cookie but the request did
* NOT arrive over HTTPS — the browser silently drops the cookie, so the next
* request has no session and the server answers "Access token required". This is
* the classic plain-HTTP install gotcha; callers surface it to the user with a
* concrete fix (use HTTPS or set COOKIE_SECURE=false) instead of a bare 401.
*/
export function willDropSecureCookie(req?: Request): boolean {
if (process.env.COOKIE_SECURE?.toLowerCase() === 'false') return false;
if (req?.secure === true) return false;
return (
process.env.NODE_ENV?.toLowerCase() === 'production' ||
process.env.FORCE_HTTPS?.toLowerCase() === 'true'
);
}
export function setAuthCookie(res: Response, token: string, req?: Request, remember?: RememberOption): void {
res.cookie(COOKIE_NAME, token, cookieOptions(false, req, remember));
}