From e619ba4d8f59aaf82b0010d769c7628fd8639bab Mon Sep 17 00:00:00 2001 From: codeplug Date: Wed, 6 Aug 2025 09:35:14 -0500 Subject: [PATCH 1/6] fix: force remove session cookies --- src/auth0-session/handlers/logout.ts | 58 +++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/src/auth0-session/handlers/logout.ts b/src/auth0-session/handlers/logout.ts index 984301378..d5f57b974 100644 --- a/src/auth0-session/handlers/logout.ts +++ b/src/auth0-session/handlers/logout.ts @@ -1,4 +1,4 @@ -import urlJoin from 'url-join'; +const urlJoin = require('url-join'); import createDebug from '../utils/debug'; import { GetConfig, LogoutOptions } from '../config'; import { SessionCache } from '../session-cache'; @@ -9,6 +9,46 @@ const debug = createDebug('logout'); export type HandleLogout = (req: Auth0Request, res: Auth0Response, options?: LogoutOptions) => Promise; +/** + * Remove a cookie by creating a matching removal header with all possible attributes + */ +function removeCookie(res: Auth0Response, cookieName: string, cookieConfig: any = {}) { + let cookieString = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 GMT`; + + // Add path (default to '/') + const path = cookieConfig.path || '/'; + cookieString += `; Path=${path}`; + + // Add domain if specified + if (cookieConfig.domain) { + cookieString += `; Domain=${cookieConfig.domain}`; + } + + // Add security attributes to match original cookie + if (cookieConfig.secure !== false) { + cookieString += '; Secure'; + } + + if (cookieConfig.httpOnly !== false) { + cookieString += '; HttpOnly'; + } + + if (cookieConfig.sameSite) { + cookieString += `; SameSite=${cookieConfig.sameSite}`; + } + + if (cookieConfig.partitioned) { + cookieString += '; Partitioned'; + } + + // Add to existing Set-Cookie headers + const existingCookies = res.res.getHeader('Set-Cookie') || []; + const cookieArray = Array.isArray(existingCookies) ? existingCookies : [existingCookies as string]; + cookieArray.push(cookieString); + + res.res.setHeader('Set-Cookie', cookieArray); +} + export default function logoutHandlerFactory( getConfig: GetConfig, getClient: GetClient, @@ -28,6 +68,7 @@ export default function logoutHandlerFactory( } const isAuthenticated = await sessionCache.isAuthenticated(req.req, res.res); + if (!isAuthenticated) { debug('end-user already logged out, redirecting to %s', returnURL); res.redirect(returnURL); @@ -35,8 +76,23 @@ export default function logoutHandlerFactory( } const idToken = await sessionCache.getIdToken(req.req, res.res); + await sessionCache.delete(req.req, res.res); + // Remove the session cookie with matching attributes + const cookieName = config.session?.name || 'appSession'; + + removeCookie(res, cookieName, config.session?.cookie); + + // Also remove with partitioned flag to ensure cleanup regardless of original cookie config + const cookieConfigWithPartitioned = { + ...config.session?.cookie, + partitioned: true + }; + removeCookie(res, cookieName, cookieConfigWithPartitioned); + + debug('session cookie cleared'); + if (!config.idpLogout) { debug('performing a local only logout, redirecting to %s', returnURL); res.redirect(returnURL); From ea8340a72dab204d462babcf9ff621b1cce352a9 Mon Sep 17 00:00:00 2001 From: codeplug Date: Wed, 6 Aug 2025 09:53:21 -0500 Subject: [PATCH 2/6] fix: fix unit tests --- src/auth0-session/handlers/logout.ts | 49 +++++++++------------------- 1 file changed, 16 insertions(+), 33 deletions(-) diff --git a/src/auth0-session/handlers/logout.ts b/src/auth0-session/handlers/logout.ts index d5f57b974..6969b7fe1 100644 --- a/src/auth0-session/handlers/logout.ts +++ b/src/auth0-session/handlers/logout.ts @@ -1,4 +1,4 @@ -const urlJoin = require('url-join'); +import urlJoin from 'url-join'; import createDebug from '../utils/debug'; import { GetConfig, LogoutOptions } from '../config'; import { SessionCache } from '../session-cache'; @@ -13,40 +13,23 @@ export type HandleLogout = (req: Auth0Request, res: Auth0Response, options?: Log * Remove a cookie by creating a matching removal header with all possible attributes */ function removeCookie(res: Auth0Response, cookieName: string, cookieConfig: any = {}) { - let cookieString = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 GMT`; - - // Add path (default to '/') - const path = cookieConfig.path || '/'; - cookieString += `; Path=${path}`; - - // Add domain if specified - if (cookieConfig.domain) { - cookieString += `; Domain=${cookieConfig.domain}`; - } - - // Add security attributes to match original cookie - if (cookieConfig.secure !== false) { - cookieString += '; Secure'; - } - - if (cookieConfig.httpOnly !== false) { - cookieString += '; HttpOnly'; - } - - if (cookieConfig.sameSite) { - cookieString += `; SameSite=${cookieConfig.sameSite}`; - } - - if (cookieConfig.partitioned) { - cookieString += '; Partitioned'; - } + const clearOptions = { + path: cookieConfig.path || '/', + domain: cookieConfig.domain, + secure: cookieConfig.secure !== false, + httpOnly: cookieConfig.httpOnly !== false, + sameSite: cookieConfig.sameSite, + partitioned: cookieConfig.partitioned + }; - // Add to existing Set-Cookie headers - const existingCookies = res.res.getHeader('Set-Cookie') || []; - const cookieArray = Array.isArray(existingCookies) ? existingCookies : [existingCookies as string]; - cookieArray.push(cookieString); + // Remove undefined values to avoid issues with cookie serialization + Object.keys(clearOptions).forEach(key => { + if (clearOptions[key as keyof typeof clearOptions] === undefined) { + delete clearOptions[key as keyof typeof clearOptions]; + } + }); - res.res.setHeader('Set-Cookie', cookieArray); + res.clearCookie(cookieName, clearOptions); } export default function logoutHandlerFactory( From f005fb888849d981009f5f938d9d977f7ac85037 Mon Sep 17 00:00:00 2001 From: codeplug Date: Wed, 6 Aug 2025 09:35:14 -0500 Subject: [PATCH 3/6] fix: force remove session cookies --- src/auth0-session/handlers/logout.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/auth0-session/handlers/logout.ts b/src/auth0-session/handlers/logout.ts index 6969b7fe1..9eaa99da0 100644 --- a/src/auth0-session/handlers/logout.ts +++ b/src/auth0-session/handlers/logout.ts @@ -1,4 +1,4 @@ -import urlJoin from 'url-join'; +const urlJoin = require('url-join'); import createDebug from '../utils/debug'; import { GetConfig, LogoutOptions } from '../config'; import { SessionCache } from '../session-cache'; @@ -23,7 +23,7 @@ function removeCookie(res: Auth0Response, cookieName: string, cookieConfig: any }; // Remove undefined values to avoid issues with cookie serialization - Object.keys(clearOptions).forEach(key => { + Object.keys(clearOptions).forEach((key) => { if (clearOptions[key as keyof typeof clearOptions] === undefined) { delete clearOptions[key as keyof typeof clearOptions]; } @@ -66,14 +66,14 @@ export default function logoutHandlerFactory( const cookieName = config.session?.name || 'appSession'; removeCookie(res, cookieName, config.session?.cookie); - + // Also remove with partitioned flag to ensure cleanup regardless of original cookie config const cookieConfigWithPartitioned = { ...config.session?.cookie, partitioned: true }; removeCookie(res, cookieName, cookieConfigWithPartitioned); - + debug('session cookie cleared'); if (!config.idpLogout) { From a911c495442bf3814da731bbc26f8aa30322fac9 Mon Sep 17 00:00:00 2001 From: codeplug Date: Wed, 6 Aug 2025 09:53:21 -0500 Subject: [PATCH 4/6] fix: fix unit tests --- src/auth0-session/handlers/logout.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auth0-session/handlers/logout.ts b/src/auth0-session/handlers/logout.ts index 9eaa99da0..36d2cf138 100644 --- a/src/auth0-session/handlers/logout.ts +++ b/src/auth0-session/handlers/logout.ts @@ -1,4 +1,4 @@ -const urlJoin = require('url-join'); +import urlJoin from 'url-join'; import createDebug from '../utils/debug'; import { GetConfig, LogoutOptions } from '../config'; import { SessionCache } from '../session-cache'; From e90ce290fcc104fda4a752be2dc63efb2f0d46a1 Mon Sep 17 00:00:00 2001 From: codeplug Date: Wed, 6 Aug 2025 10:54:54 -0500 Subject: [PATCH 5/6] fix: set partitioned flag as default --- src/auth0-session/handlers/logout.ts | 47 +++++++++++++------ src/auth0-session/session/abstract-session.ts | 5 +- .../session/stateless-session.ts | 3 +- src/auth0-session/utils/cookies.ts | 3 +- 4 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/auth0-session/handlers/logout.ts b/src/auth0-session/handlers/logout.ts index 36d2cf138..5806c85b6 100644 --- a/src/auth0-session/handlers/logout.ts +++ b/src/auth0-session/handlers/logout.ts @@ -13,23 +13,40 @@ export type HandleLogout = (req: Auth0Request, res: Auth0Response, options?: Log * Remove a cookie by creating a matching removal header with all possible attributes */ function removeCookie(res: Auth0Response, cookieName: string, cookieConfig: any = {}) { - const clearOptions = { - path: cookieConfig.path || '/', - domain: cookieConfig.domain, - secure: cookieConfig.secure !== false, - httpOnly: cookieConfig.httpOnly !== false, - sameSite: cookieConfig.sameSite, - partitioned: cookieConfig.partitioned - }; + let cookieString = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 GMT`; - // Remove undefined values to avoid issues with cookie serialization - Object.keys(clearOptions).forEach((key) => { - if (clearOptions[key as keyof typeof clearOptions] === undefined) { - delete clearOptions[key as keyof typeof clearOptions]; - } - }); + // Add path (default to '/') + const path = cookieConfig.path || '/'; + cookieString += `; Path=${path}`; + + // Add domain if specified + if (cookieConfig.domain) { + cookieString += `; Domain=${cookieConfig.domain}`; + } + + // Add security attributes to match original cookie + if (cookieConfig.secure !== false) { + cookieString += '; Secure'; + } + + if (cookieConfig.httpOnly !== false) { + cookieString += '; HttpOnly'; + } + + if (cookieConfig.sameSite) { + cookieString += `; SameSite=${cookieConfig.sameSite}`; + } + + if (cookieConfig.partitioned) { + cookieString += '; Partitioned'; + } + + // Add to existing Set-Cookie headers + const existingCookies = res.res.getHeader('Set-Cookie') || []; + const cookieArray = Array.isArray(existingCookies) ? existingCookies : [existingCookies as string]; + cookieArray.push(cookieString); - res.clearCookie(cookieName, clearOptions); + res.res.setHeader('Set-Cookie', cookieArray); } export default function logoutHandlerFactory( diff --git a/src/auth0-session/session/abstract-session.ts b/src/auth0-session/session/abstract-session.ts index 7b864f080..681e78241 100644 --- a/src/auth0-session/session/abstract-session.ts +++ b/src/auth0-session/session/abstract-session.ts @@ -106,7 +106,7 @@ export abstract class AbstractSession { } = config.session; if (!session) { - await this.deleteSession(req, res, cookieConfig); + await this.deleteSession(req, res, { ...cookieConfig, partitioned: true }); return; } @@ -116,7 +116,8 @@ export abstract class AbstractSession { const exp = this.calculateExp(iat, uat, config); const cookieOptions: CookieSerializeOptions = { - ...cookieConfig + ...cookieConfig, + partitioned: true }; if (!transient) { cookieOptions.expires = new Date(exp * 1000); diff --git a/src/auth0-session/session/stateless-session.ts b/src/auth0-session/session/stateless-session.ts index fc4fb6787..f7002420b 100644 --- a/src/auth0-session/session/stateless-session.ts +++ b/src/auth0-session/session/stateless-session.ts @@ -31,7 +31,8 @@ export class StatelessSession< name: sessionName } = config.session; const cookieOptions: CookieSerializeOptions = { - ...cookieConfig + ...cookieConfig, + partitioned: true }; if (!transient) { cookieOptions.expires = new Date(); diff --git a/src/auth0-session/utils/cookies.ts b/src/auth0-session/utils/cookies.ts index 60fce4b46..842d2b1f2 100644 --- a/src/auth0-session/utils/cookies.ts +++ b/src/auth0-session/utils/cookies.ts @@ -9,8 +9,7 @@ export abstract class Cookies { } set(name: string, value: string, options: CookieSerializeOptions = {}): void { - let cookieString = serialize(name, value, options); - cookieString += '; Partitioned'; + const cookieString = serialize(name, value, options); this.cookies.push(cookieString); } From d8e1be57dc606cce40f22ff4e3ebceffe3e03ab0 Mon Sep 17 00:00:00 2001 From: codeplug Date: Wed, 6 Aug 2025 10:59:00 -0500 Subject: [PATCH 6/6] fix: fix typescript --- src/auth0-session/session/abstract-session.ts | 10 +++++++--- src/auth0-session/session/stateful-session.ts | 8 ++++++-- src/auth0-session/session/stateless-session.ts | 8 ++++++-- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/auth0-session/session/abstract-session.ts b/src/auth0-session/session/abstract-session.ts index 681e78241..02f62c8fe 100644 --- a/src/auth0-session/session/abstract-session.ts +++ b/src/auth0-session/session/abstract-session.ts @@ -1,5 +1,9 @@ import createDebug from '../utils/debug'; import { CookieSerializeOptions } from 'cookie'; + +interface ExtendedCookieOptions extends CookieSerializeOptions { + partitioned?: boolean; +} import { Config, GetConfig } from '../config'; import { Auth0RequestCookies, Auth0ResponseCookies } from '../http'; @@ -51,14 +55,14 @@ export abstract class AbstractSession { uat: number, iat: number, exp: number, - cookieOptions: CookieSerializeOptions, + cookieOptions: ExtendedCookieOptions, isNewSession: boolean ): Promise; abstract deleteSession( req: Auth0RequestCookies, res: Auth0ResponseCookies, - cookieOptions: CookieSerializeOptions + cookieOptions: ExtendedCookieOptions ): Promise; public async read(req: Auth0RequestCookies): Promise<[Session?, number?]> { @@ -115,7 +119,7 @@ export abstract class AbstractSession { const iat = typeof createdAt === 'number' ? createdAt : uat; const exp = this.calculateExp(iat, uat, config); - const cookieOptions: CookieSerializeOptions = { + const cookieOptions: ExtendedCookieOptions = { ...cookieConfig, partitioned: true }; diff --git a/src/auth0-session/session/stateful-session.ts b/src/auth0-session/session/stateful-session.ts index 8590a9642..c7dc67d86 100644 --- a/src/auth0-session/session/stateful-session.ts +++ b/src/auth0-session/session/stateful-session.ts @@ -1,4 +1,8 @@ import { CookieSerializeOptions } from 'cookie'; + +interface ExtendedCookieOptions extends CookieSerializeOptions { + partitioned?: boolean; +} import createDebug from '../utils/debug'; import { AbstractSession, SessionPayload } from './abstract-session'; import { generateCookieValue, getCookieValue } from '../utils/signed-cookies'; @@ -69,7 +73,7 @@ export class StatefulSession< uat: number, iat: number, exp: number, - cookieOptions: CookieSerializeOptions, + cookieOptions: ExtendedCookieOptions, isNewSession: boolean ): Promise { const config = await this.getConfig(req); @@ -103,7 +107,7 @@ export class StatefulSession< async deleteSession( req: Auth0RequestCookies, res: Auth0ResponseCookies, - cookieOptions: CookieSerializeOptions + cookieOptions: ExtendedCookieOptions ): Promise { const config = await this.getConfig(req); const { name: sessionName } = config.session; diff --git a/src/auth0-session/session/stateless-session.ts b/src/auth0-session/session/stateless-session.ts index f7002420b..bbf8ff170 100644 --- a/src/auth0-session/session/stateless-session.ts +++ b/src/auth0-session/session/stateless-session.ts @@ -1,5 +1,9 @@ import * as jose from 'jose'; import { CookieSerializeOptions, serialize } from 'cookie'; + +interface ExtendedCookieOptions extends CookieSerializeOptions { + partitioned?: boolean; +} import createDebug from '../utils/debug'; import { Config } from '../config'; import { encryption } from '../utils/hkdf'; @@ -117,7 +121,7 @@ export class StatelessSession< uat: number, iat: number, exp: number, - cookieOptions: CookieSerializeOptions + cookieOptions: ExtendedCookieOptions ): Promise { const config = await this.getConfig(req); const { name: sessionName } = config.session; @@ -155,7 +159,7 @@ export class StatelessSession< async deleteSession( req: Auth0RequestCookies, res: Auth0ResponseCookies, - cookieOptions: CookieSerializeOptions + cookieOptions: ExtendedCookieOptions ): Promise { const config = await this.getConfig(req); const { name: sessionName } = config.session;