[GH-ISSUE #4626] [BUG] lastLoginMethod plugin sets cookie even on failed authentication attempts #18631

Closed
opened 2026-04-15 17:11:35 -05:00 by GiteaMirror · 1 comment
Owner

Originally created by @Maxime-RiseVerse on GitHub (Sep 13, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/4626

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Configure Better Auth with lastLoginMethod plugin
  2. Attempt to sign in with invalid credentials at /sign-in/email
  3. Authentication fails with 401 error
  4. Check browser cookies better-auth.last_used_login_method cookie is set to "email" despite authentication failure

Current vs. Expected behavior

Following the steps from the previous section, I expected the cookie to NOT be set when authentication fails, but I observed the cookie being set regardless of authentication success.

The plugin's after hook runs on every request and sets the cookie whenever a login method is detected in the path, without checking if authentication succeeded:

  after: [
    {
      matcher() {
        return true; // Runs for ALL routes
      },
      handler: createAuthMiddleware(async (ctx) => {
        const lastUsedLoginMethod =
  config.customResolveMethod(ctx);
        lastUsedLoginMethod &&
          ctx.setCookie(config.cookieName,
  lastUsedLoginMethod, {
            maxAge: config.maxAge,
            secure: false,
            httpOnly: false,
            path: "/",
          });
      }),
    },
  ]

Source:
https://github.com/better-auth/better-auth/blob/main/packages/better-auth/src/plugins/last-login-method/index.ts

What version of Better Auth are you using?

1.3.9

System info

{
  "system": {
    "platform": "win32",
    "arch": "x64",
    "version": "Windows 11 Pro",
    "release": "10.0.26100",
    "cpuCount": 16,
    "cpuModel": "Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz",
    "totalMemory": "31.88 GB",
    "freeMemory": "20.00 GB"
  },
  "node": {
    "version": "v22.14.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "10.9.2"
  },
  "frameworks": [
    {
      "name": "next",
      "version": "15.5.2"
    },
    {
      "name": "react",
      "version": "19.1.1"
    }
  ],
  "databases": [
    {
      "name": "pg",
      "version": "^8.16.3"
    },
    {
      "name": "drizzle",
      "version": "^0.44.5"
    }
  ],
  "betterAuth": {
    "version": "0.1.0",
    "config": null
  }
}

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

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth";
  import { lastLoginMethod } from "better-auth/plugins";

  export const auth = betterAuth({
    emailAndPassword: {
      enabled: true
    },
    plugins: [
      lastLoginMethod()
    ]
  });

Additional context

The cookie should only be set after successful authentication. This causes incorrect UX where failed login attempts are remembered as the "last used" method. No tests exist for failed authentication scenarios in the plugin's test file.

Originally created by @Maxime-RiseVerse on GitHub (Sep 13, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/4626 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Configure Better Auth with `lastLoginMethod` plugin 2. Attempt to sign in with invalid credentials at /sign-in/email 3. Authentication fails with 401 error 4. Check browser cookies `better-auth.last_used_login_method` cookie is set to "email" despite authentication failure ### Current vs. Expected behavior Following the steps from the previous section, I expected the cookie to NOT be set when authentication fails, but I observed the cookie being set regardless of authentication success. The plugin's after hook runs on every request and sets the cookie whenever a login method is detected in the path, without checking if authentication succeeded: ``` after: [ { matcher() { return true; // Runs for ALL routes }, handler: createAuthMiddleware(async (ctx) => { const lastUsedLoginMethod = config.customResolveMethod(ctx); lastUsedLoginMethod && ctx.setCookie(config.cookieName, lastUsedLoginMethod, { maxAge: config.maxAge, secure: false, httpOnly: false, path: "/", }); }), }, ] ``` Source: https://github.com/better-auth/better-auth/blob/main/packages/better-auth/src/plugins/last-login-method/index.ts ### What version of Better Auth are you using? 1.3.9 ### System info ```bash { "system": { "platform": "win32", "arch": "x64", "version": "Windows 11 Pro", "release": "10.0.26100", "cpuCount": 16, "cpuModel": "Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz", "totalMemory": "31.88 GB", "freeMemory": "20.00 GB" }, "node": { "version": "v22.14.0", "env": "development" }, "packageManager": { "name": "npm", "version": "10.9.2" }, "frameworks": [ { "name": "next", "version": "15.5.2" }, { "name": "react", "version": "19.1.1" } ], "databases": [ { "name": "pg", "version": "^8.16.3" }, { "name": "drizzle", "version": "^0.44.5" } ], "betterAuth": { "version": "0.1.0", "config": null } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth"; import { lastLoginMethod } from "better-auth/plugins"; export const auth = betterAuth({ emailAndPassword: { enabled: true }, plugins: [ lastLoginMethod() ] }); ``` ### Additional context The cookie should only be set after successful authentication. This causes incorrect UX where failed login attempts are remembered as the "last used" method. No tests exist for failed authentication scenarios in the plugin's test file.
GiteaMirror added the lockedbug labels 2026-04-15 17:11:35 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Sep 13, 2025):

You're correct: the lastLoginMethod plugin currently sets the cookie even on failed authentication attempts because its after hook runs on every request and doesn't check if authentication succeeded. This is confirmed in the plugin's code—there's no conditional logic to verify authentication success before setting the cookie, which leads to the behavior you described (source).

To fix this, you can modify the plugin's after hook to check for authentication success before setting the cookie. In Better Auth, successful authentication is typically indicated by the presence of a valid session or user in the context object. You can follow the pattern used in other plugins (like magic-link), which only set cookies after confirming authentication success (source).

For example, you could update the handler like this:

handler: createAuthMiddleware(async (ctx) => {
  const lastUsedLoginMethod = config.customResolveMethod(ctx);
  // Only set the cookie if authentication succeeded (e.g., session or user exists)
  if (lastUsedLoginMethod && ctx.session) {
    ctx.setCookie(config.cookieName, lastUsedLoginMethod, {
      maxAge: config.maxAge,
      secure: false,
      httpOnly: false,
      path: "/",
    });
  }
}),

You may need to adjust the condition depending on how your context exposes authentication state (e.g., ctx.session, ctx.user, or similar).

Also, there are currently no tests for failed authentication scenarios in the plugin's test file. It's a good idea to add tests to ensure the cookie is not set when authentication fails.

There are no existing tickets or docs addressing this issue, and recent PRs have not fixed it (source). This workaround should help you get the expected behavior.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

<!-- gh-comment-id:3287898448 --> @dosubot[bot] commented on GitHub (Sep 13, 2025): <!-- Answer --> You're correct: the lastLoginMethod plugin currently sets the cookie even on failed authentication attempts because its after hook runs on every request and doesn't check if authentication succeeded. This is confirmed in the plugin's code—there's no conditional logic to verify authentication success before setting the cookie, which leads to the behavior you described [(source)](https://github.com/better-auth/better-auth/blob/47e3c5fba591de7c208556465d3bf31a14128584/packages/better-auth/src/plugins/last-login-method/index.ts). To fix this, you can modify the plugin's after hook to check for authentication success before setting the cookie. In Better Auth, successful authentication is typically indicated by the presence of a valid session or user in the context object. You can follow the pattern used in other plugins (like magic-link), which only set cookies after confirming authentication success [(source)](https://github.com/better-auth/better-auth/blob/47e3c5fba591de7c208556465d3bf31a14128584/packages/better-auth/src/plugins/magic-link/index.ts). For example, you could update the handler like this: ```typescript handler: createAuthMiddleware(async (ctx) => { const lastUsedLoginMethod = config.customResolveMethod(ctx); // Only set the cookie if authentication succeeded (e.g., session or user exists) if (lastUsedLoginMethod && ctx.session) { ctx.setCookie(config.cookieName, lastUsedLoginMethod, { maxAge: config.maxAge, secure: false, httpOnly: false, path: "/", }); } }), ``` You may need to adjust the condition depending on how your context exposes authentication state (e.g., `ctx.session`, `ctx.user`, or similar). Also, there are currently no tests for failed authentication scenarios in the plugin's test file. It's a good idea to add tests to ensure the cookie is not set when authentication fails. There are no existing tickets or docs addressing this issue, and recent PRs have not fixed it [(source)](https://github.com/better-auth/better-auth/pull/4572). This workaround should help you get the expected behavior. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/02210495-1db3-41e7-8085-68c89cd30c35?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/02210495-1db3-41e7-8085-68c89cd30c35?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/02210495-1db3-41e7-8085-68c89cd30c35?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/02210495-1db3-41e7-8085-68c89cd30c35?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/02210495-1db3-41e7-8085-68c89cd30c35?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/02210495-1db3-41e7-8085-68c89cd30c35?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/02210495-1db3-41e7-8085-68c89cd30c35?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/4626)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#18631