fix(auth): logout actually clears the session cookie in Chrome
All checks were successful
Deploy to Production / deploy (push) Successful in 53s
All checks were successful
Deploy to Production / deploy (push) Successful in 53s
The clearCookie call on /v1/auth/logout was passing only {path:'/'},
missing the httpOnly + sameSite + secure flags the setCookie used. In
production (secure=true), Chrome treats a Set-Cookie clear directive
without Secure as a *different* cookie — it creates an empty insecure
cookie and leaves the original Secure session cookie in place. Result:
users who clicked "Sign out" stayed logged in for the full 30-day
session lifetime in the browser's view (DB session was destroyed
correctly; only the cookie persisted).
Now both setCookie and clearCookie pull from sessionCookieOpts() so
the attributes can't drift apart again.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
091454d273
commit
1cccdbdff1
@ -20,6 +20,29 @@ import { sendSms, smsConfigured } from '../lib/sms.js';
|
|||||||
const SESSION_COOKIE = 'bmm_session';
|
const SESSION_COOKIE = 'bmm_session';
|
||||||
const OAUTH_STATE_COOKIE = 'bmm_oauth_state';
|
const OAUTH_STATE_COOKIE = 'bmm_oauth_state';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single source of truth for the session cookie's flags. setCookie AND
|
||||||
|
* clearCookie MUST agree on (path, sameSite, secure, httpOnly) — when they
|
||||||
|
* drift, Chrome treats the clear directive as a brand-new cookie with
|
||||||
|
* different security attributes and silently leaves the original one in
|
||||||
|
* place. That's what bit us until now: logout looked successful (200 OK
|
||||||
|
* with a Set-Cookie clear) but the real session cookie persisted because
|
||||||
|
* the clear omitted Secure+HttpOnly.
|
||||||
|
*/
|
||||||
|
function sessionCookieOpts(): {
|
||||||
|
httpOnly: true;
|
||||||
|
sameSite: 'lax';
|
||||||
|
path: '/';
|
||||||
|
secure: boolean;
|
||||||
|
} {
|
||||||
|
return {
|
||||||
|
httpOnly: true,
|
||||||
|
sameSite: 'lax',
|
||||||
|
path: '/',
|
||||||
|
secure: config.NODE_ENV === 'production',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const GoogleClaims = z.object({
|
const GoogleClaims = z.object({
|
||||||
iss: z.string(),
|
iss: z.string(),
|
||||||
aud: z.string(),
|
aud: z.string(),
|
||||||
@ -229,7 +252,7 @@ export async function authRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
const token = req.cookies[SESSION_COOKIE];
|
const token = req.cookies[SESSION_COOKIE];
|
||||||
const session = token ? await getSession(token) : null;
|
const session = token ? await getSession(token) : null;
|
||||||
if (token) await destroySession(token);
|
if (token) await destroySession(token);
|
||||||
reply.clearCookie(SESSION_COOKIE, { path: '/' });
|
reply.clearCookie(SESSION_COOKIE, sessionCookieOpts());
|
||||||
if (session) {
|
if (session) {
|
||||||
await audit({
|
await audit({
|
||||||
orgId: session.orgId,
|
orgId: session.orgId,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user