Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions src/auth0-session/handlers/logout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,46 @@ const debug = createDebug('logout');

export type HandleLogout = (req: Auth0Request, res: Auth0Response, options?: LogoutOptions) => Promise<void>;

/**
* 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,
Expand All @@ -28,15 +68,31 @@ 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);
return;
}

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);
Expand Down
15 changes: 10 additions & 5 deletions src/auth0-session/session/abstract-session.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -51,14 +55,14 @@ export abstract class AbstractSession<Session> {
uat: number,
iat: number,
exp: number,
cookieOptions: CookieSerializeOptions,
cookieOptions: ExtendedCookieOptions,
isNewSession: boolean
): Promise<void>;

abstract deleteSession(
req: Auth0RequestCookies,
res: Auth0ResponseCookies,
cookieOptions: CookieSerializeOptions
cookieOptions: ExtendedCookieOptions
): Promise<void>;

public async read(req: Auth0RequestCookies): Promise<[Session?, number?]> {
Expand Down Expand Up @@ -106,7 +110,7 @@ export abstract class AbstractSession<Session> {
} = config.session;

if (!session) {
await this.deleteSession(req, res, cookieConfig);
await this.deleteSession(req, res, { ...cookieConfig, partitioned: true });
return;
}

Expand All @@ -115,8 +119,9 @@ export abstract class AbstractSession<Session> {
const iat = typeof createdAt === 'number' ? createdAt : uat;
const exp = this.calculateExp(iat, uat, config);

const cookieOptions: CookieSerializeOptions = {
...cookieConfig
const cookieOptions: ExtendedCookieOptions = {
...cookieConfig,
partitioned: true
};
if (!transient) {
cookieOptions.expires = new Date(exp * 1000);
Expand Down
8 changes: 6 additions & 2 deletions src/auth0-session/session/stateful-session.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -69,7 +73,7 @@ export class StatefulSession<
uat: number,
iat: number,
exp: number,
cookieOptions: CookieSerializeOptions,
cookieOptions: ExtendedCookieOptions,
isNewSession: boolean
): Promise<void> {
const config = await this.getConfig(req);
Expand Down Expand Up @@ -103,7 +107,7 @@ export class StatefulSession<
async deleteSession(
req: Auth0RequestCookies,
res: Auth0ResponseCookies,
cookieOptions: CookieSerializeOptions
cookieOptions: ExtendedCookieOptions
): Promise<void> {
const config = await this.getConfig(req);
const { name: sessionName } = config.session;
Expand Down
11 changes: 8 additions & 3 deletions src/auth0-session/session/stateless-session.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -31,7 +35,8 @@ export class StatelessSession<
name: sessionName
} = config.session;
const cookieOptions: CookieSerializeOptions = {
...cookieConfig
...cookieConfig,
partitioned: true
};
if (!transient) {
cookieOptions.expires = new Date();
Expand Down Expand Up @@ -116,7 +121,7 @@ export class StatelessSession<
uat: number,
iat: number,
exp: number,
cookieOptions: CookieSerializeOptions
cookieOptions: ExtendedCookieOptions
): Promise<void> {
const config = await this.getConfig(req);
const { name: sessionName } = config.session;
Expand Down Expand Up @@ -154,7 +159,7 @@ export class StatelessSession<
async deleteSession(
req: Auth0RequestCookies,
res: Auth0ResponseCookies,
cookieOptions: CookieSerializeOptions
cookieOptions: ExtendedCookieOptions
): Promise<void> {
const config = await this.getConfig(req);
const { name: sessionName } = config.session;
Expand Down
3 changes: 1 addition & 2 deletions src/auth0-session/utils/cookies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Loading