Disabling 2FA does not update JWT Cookie Cache, whereas Enabling 2FA does. #514

Closed
opened 2026-03-13 07:50:32 -05:00 by GiteaMirror · 3 comments
Owner

Originally created by @Scooter1337 on GitHub (Jan 2, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Enable cookie cache and enable 2fa, the cookie will be updated to mirror the changed field.
Disable 2fa and note that the cookie does not change, until the maxage is lived.

Current vs. Expected behavior

Both enabling and disabling should result in an updated cookie :)

What version of Better Auth are you using?

1.1.7

Provide environment information

-

Which area(s) are affected? (Select all that apply)

Backend

Auth config (if applicable)

No response

Additional context

No response

Originally created by @Scooter1337 on GitHub (Jan 2, 2025). ### Is this suited for github? - [X] Yes, this is suited for github ### To Reproduce Enable cookie cache and enable 2fa, the cookie will be updated to mirror the changed field. Disable 2fa and note that the cookie does not change, until the maxage is lived. ### Current vs. Expected behavior Both enabling and disabling should result in an updated cookie :) ### What version of Better Auth are you using? 1.1.7 ### Provide environment information ```bash - ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) _No response_ ### Additional context _No response_
GiteaMirror added the bug label 2026-03-13 07:50:32 -05:00
Author
Owner

@Scooter1337 commented on GitHub (Jan 4, 2025):

I believe it's as easy as changing:

Lines 190-232 of two-factor/index.ts

async (ctx) => {
	const user = ctx.context.session.user as UserWithTwoFactor;
	const { password } = ctx.body;
	const isPasswordValid = await validatePassword(ctx, {
		password,
		userId: user.id,
	});
	if (!isPasswordValid) {
		throw new APIError("BAD_REQUEST", {
			message: "Invalid password",
		});
	}
	await ctx.context.internalAdapter.updateUser(user.id, {
		twoFactorEnabled: false,
	});
	await ctx.context.adapter.delete({
		model: opts.twoFactorTable,
		where: [
			{
			field: "userId",
			value: user.id,
			},
		],
	});
	const newSession = await ctx.context.internalAdapter.createSession(
		user.id,
		ctx.request,
		false,
		ctx.context.session.session,
	);
	/**
	 * Update the session cookie with the new user data
	 */
	await setSessionCookie(ctx, {
		session: newSession,
		user,
	});
	//remove current session
	await ctx.context.internalAdapter.deleteSession(
		ctx.context.session.session.token,
	);
	return ctx.json({ status: true });
},

to

async (ctx) => {
	const user = ctx.context.session.user as UserWithTwoFactor;
	const { password } = ctx.body;
	const isPasswordValid = await validatePassword(ctx, {
		password,
		userId: user.id,
	});
	if (!isPasswordValid) {
		throw new APIError("BAD_REQUEST", {
			message: "Invalid password",
		});
	}
	const updatedUser = await ctx.context.internalAdapter.updateUser(user.id, { // CHANGED
		twoFactorEnabled: false,
	});
	await ctx.context.adapter.delete({
		model: opts.twoFactorTable,
		where: [
			{
			field: "userId",
			value: user.id,
			},
		],
	});
	const newSession = await ctx.context.internalAdapter.createSession(
		updatedUser.id, // CHANGED
		ctx.request,
		false,
		ctx.context.session.session,
	);
	/**
	 * Update the session cookie with the new user data
	 */
	await setSessionCookie(ctx, {
		session: newSession,
		user: updatedUser, // CHANGED
	});
	//remove current session
	await ctx.context.internalAdapter.deleteSession(
		ctx.context.session.session.token,
	);
	return ctx.json({ status: true });
},

All changed lines are marked with // CHANGED

@Scooter1337 commented on GitHub (Jan 4, 2025): I believe it's as easy as changing: Lines 190-232 of two-factor/index.ts ```typescript async (ctx) => { const user = ctx.context.session.user as UserWithTwoFactor; const { password } = ctx.body; const isPasswordValid = await validatePassword(ctx, { password, userId: user.id, }); if (!isPasswordValid) { throw new APIError("BAD_REQUEST", { message: "Invalid password", }); } await ctx.context.internalAdapter.updateUser(user.id, { twoFactorEnabled: false, }); await ctx.context.adapter.delete({ model: opts.twoFactorTable, where: [ { field: "userId", value: user.id, }, ], }); const newSession = await ctx.context.internalAdapter.createSession( user.id, ctx.request, false, ctx.context.session.session, ); /** * Update the session cookie with the new user data */ await setSessionCookie(ctx, { session: newSession, user, }); //remove current session await ctx.context.internalAdapter.deleteSession( ctx.context.session.session.token, ); return ctx.json({ status: true }); }, ``` to ```typescript async (ctx) => { const user = ctx.context.session.user as UserWithTwoFactor; const { password } = ctx.body; const isPasswordValid = await validatePassword(ctx, { password, userId: user.id, }); if (!isPasswordValid) { throw new APIError("BAD_REQUEST", { message: "Invalid password", }); } const updatedUser = await ctx.context.internalAdapter.updateUser(user.id, { // CHANGED twoFactorEnabled: false, }); await ctx.context.adapter.delete({ model: opts.twoFactorTable, where: [ { field: "userId", value: user.id, }, ], }); const newSession = await ctx.context.internalAdapter.createSession( updatedUser.id, // CHANGED ctx.request, false, ctx.context.session.session, ); /** * Update the session cookie with the new user data */ await setSessionCookie(ctx, { session: newSession, user: updatedUser, // CHANGED }); //remove current session await ctx.context.internalAdapter.deleteSession( ctx.context.session.session.token, ); return ctx.json({ status: true }); }, ``` All changed lines are marked with `// CHANGED`
Author
Owner

@Scooter1337 commented on GitHub (Jan 4, 2025):

BTW: the enable (with skipVerification enabled) also has this issue, but only the following line needs changing.
I don't use skipVerification so I didn't notice this. It seems verify is the only route that does it correctly.

lines 117-120 of two-factor/index.ts:

await setSessionCookie(ctx, {
	session: newSession,
	user,
});

to

await setSessionCookie(ctx, {
	session: newSession,
	user: updatedUser,
});
@Scooter1337 commented on GitHub (Jan 4, 2025): BTW: the enable (with skipVerification enabled) also has this issue, but only the following line needs changing. I don't use skipVerification so I didn't notice this. It seems verify is the only route that does it correctly. lines 117-120 of two-factor/index.ts: ```typescript await setSessionCookie(ctx, { session: newSession, user, }); ``` to ```typescript await setSessionCookie(ctx, { session: newSession, user: updatedUser, }); ```
Author
Owner

@Scooter1337 commented on GitHub (Jan 4, 2025):

PS: Why not one function that takes in the context and whether to set the twoFactorEnabled to true or false, to make these routes more DRY?

@Scooter1337 commented on GitHub (Jan 4, 2025): PS: Why not one function that takes in the context and whether to set the twoFactorEnabled to true or false, to make these routes more DRY?
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#514