diff --git a/packages/better-auth/src/plugins/two-factor/two-factor.test.ts b/packages/better-auth/src/plugins/two-factor/two-factor.test.ts index 441ef2b07f..858c91939e 100644 --- a/packages/better-auth/src/plugins/two-factor/two-factor.test.ts +++ b/packages/better-auth/src/plugins/two-factor/two-factor.test.ts @@ -322,10 +322,30 @@ describe("two factor", async () => { expect(currentBackupCodes.backupCodes).toBeDefined(); expect(currentBackupCodes.backupCodes).not.toContain(backupCode); + // Start a new 2FA session to test invalid backup code + const headers2 = new Headers(); + await client.signIn.email({ + email: testUser.email, + password: testUser.password, + fetchOptions: { + onSuccess(context) { + const parsed = parseSetCookieHeader( + context.response.headers.get("Set-Cookie") || "", + ); + headers2.append( + "cookie", + `better-auth.two_factor=${ + parsed.get("better-auth.two_factor")?.value + }`, + ); + }, + }, + }); + const res = await client.twoFactor.verifyBackupCode({ code: "invalid-code", fetchOptions: { - headers, + headers: headers2, onSuccess(context) { const parsed = parseSetCookieHeader( context.response.headers.get("Set-Cookie") || "", diff --git a/packages/better-auth/src/plugins/two-factor/verify-two-factor.ts b/packages/better-auth/src/plugins/two-factor/verify-two-factor.ts index 2def816c63..29c24a7107 100644 --- a/packages/better-auth/src/plugins/two-factor/verify-two-factor.ts +++ b/packages/better-auth/src/plugins/two-factor/verify-two-factor.ts @@ -60,10 +60,18 @@ export async function verifyTwoFactor(ctx: GenericEndpointContext) { message: "failed to create session", }); } + // Delete the verification token from the database after successful verification + await ctx.context.internalAdapter.deleteVerificationValue( + verificationToken.id, + ); await setSessionCookie(ctx, { session, user, }); + // Always clear the two factor cookie after successful verification + ctx.setCookie(cookieName.name, "", { + maxAge: 0, + }); if (ctx.body.trustDevice) { const trustDeviceCookie = ctx.context.createAuthCookie( TRUST_DEVICE_COOKIE_NAME, @@ -89,10 +97,6 @@ export async function verifyTwoFactor(ctx: GenericEndpointContext) { ctx.setCookie(ctx.context.authCookies.dontRememberToken.name, "", { maxAge: 0, }); - // delete the two factor cookie - ctx.setCookie(cookieName.name, "", { - maxAge: 0, - }); } return ctx.json({ token: session.token,