From e297caeb42fd378ae366d2efd38348d8145d7f0a Mon Sep 17 00:00:00 2001 From: KinfeMichael Tariku <65047246+Kinfe123@users.noreply.github.com> Date: Mon, 15 Sep 2025 19:00:50 +0300 Subject: [PATCH] fix: prevent lastLoginMethod plugin from setting cookie on failed auth (#4673) --- .../src/plugins/last-login-method/index.ts | 31 +++++++++------ .../last-login-method.test.ts | 39 +++++++++++++++++++ 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/packages/better-auth/src/plugins/last-login-method/index.ts b/packages/better-auth/src/plugins/last-login-method/index.ts index 60e2d95c1c..a13c55df6f 100644 --- a/packages/better-auth/src/plugins/last-login-method/index.ts +++ b/packages/better-auth/src/plugins/last-login-method/index.ts @@ -98,19 +98,26 @@ export const lastLoginMethod = ( handler: createAuthMiddleware(async (ctx) => { const lastUsedLoginMethod = config.customResolveMethod(ctx); if (lastUsedLoginMethod) { - // Inherit cookie attributes from Better Auth's centralized cookie system - // This ensures consistency with cross-origin, cross-subdomain, and security settings - const cookieAttributes = { - ...ctx.context.authCookies.sessionToken.options, - maxAge: config.maxAge, - httpOnly: false, // Override: plugin cookies are not httpOnly - }; + const setCookie = ctx.context.responseHeaders?.get("set-cookie"); + const sessionTokenName = + ctx.context.authCookies.sessionToken.name; + const hasSessionToken = + setCookie && setCookie.includes(sessionTokenName); + if (hasSessionToken) { + // Inherit cookie attributes from Better Auth's centralized cookie system + // This ensures consistency with cross-origin, cross-subdomain, and security settings + const cookieAttributes = { + ...ctx.context.authCookies.sessionToken.options, + maxAge: config.maxAge, + httpOnly: false, // Override: plugin cookies are not httpOnly + }; - ctx.setCookie( - config.cookieName, - lastUsedLoginMethod, - cookieAttributes, - ); + ctx.setCookie( + config.cookieName, + lastUsedLoginMethod, + cookieAttributes, + ); + } } }), }, diff --git a/packages/better-auth/src/plugins/last-login-method/last-login-method.test.ts b/packages/better-auth/src/plugins/last-login-method/last-login-method.test.ts index e00807a584..c69acd56a1 100644 --- a/packages/better-auth/src/plugins/last-login-method/last-login-method.test.ts +++ b/packages/better-auth/src/plugins/last-login-method/last-login-method.test.ts @@ -51,4 +51,43 @@ describe("lastLoginMethod", async () => { }); expect(session?.user.lastLoginMethod).toBe("email"); }); + + it("should NOT set the last login method cookie on failed authentication", async () => { + const headers = new Headers(); + const response = await client.signIn.email( + { + email: testUser.email, + password: "wrong-password", + }, + { + onError(context) { + cookieSetter(headers)(context); + }, + }, + ); + + expect(response.error).toBeDefined(); + + const cookies = parseCookies(headers.get("cookie") || ""); + expect(cookies.get("better-auth.last_used_login_method")).toBeUndefined(); + }); + + it("should NOT set the last login method cookie on failed OAuth callback", async () => { + const headers = new Headers(); + const response = await client.$fetch("/callback/google", { + method: "GET", + query: { + code: "invalid-code", + state: "invalid-state", + }, + onError(context) { + cookieSetter(headers)(context); + }, + }); + + expect(response.error).toBeDefined(); + + const cookies = parseCookies(headers.get("cookie") || ""); + expect(cookies.get("better-auth.last_used_login_method")).toBeUndefined(); + }); });