[GH-ISSUE #7607] session_data cookie not regenerating after expiry when using twoFactor plugin #19482

Open
opened 2026-04-15 18:37:45 -05:00 by GiteaMirror · 3 comments
Owner

Originally created by @Dnori96 on GitHub (Jan 25, 2026).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/7607

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Little description of the bug

When using cookieCache with the twoFactor plugin, the session_data cookie is not regenerated after it expires. The session_token cookie persists, but once session_data expires (after maxAge), the user is logged out because the session cannot be restored.

Additionally, disabling cookieCache entirely causes TOTP verification to fail with "invalid two factor cookie" error, suggesting the twoFactor plugin depends on cookieCache to store the pending 2FA state.

To reproduce

  1. Configure better-auth with cookieCache and twoFactor plugin
  2. Sign in with a user that has 2FA enabled
  3. Complete TOTP verification successfully
  4. User is redirected to a protected page, both session_token and session_data cookies are set
  5. Wait for cookieCache.maxAge to expire (e.g., depending of the maxAge in auth config)
  6. Refresh the page or navigate
  7. session_data cookie disappears and is never regenerated
  8. User is logged out despite session_token still existing

Current vs. Expected behavior

After session_data expires, calling getSession() should:

  1. Validate the session_token against the database
  2. Regenerate the session_data cookie with fresh cached data

What version of Better Auth are you using?

1.4.17

System info

"node": {
    "version": "v22.20.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "11.7.0"
  },
  "frameworks": [
    {
      "name": "next",
      "version": "^16.0.7"
    },
    {
      "name": "react",
      "version": "^19.2.3"
    }
  ],
  "databases": [
    {
      "name": "pg",
      "version": "^8.16.3"
    },
    {
      "name": "@prisma/client",
      "version": "^7.3.0"
    }
  ],
  "betterAuth": {
    "version": "1.4.17",
    "config": null,
  }

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

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { prisma } from "./db/prisma";
import { nextCookies } from "better-auth/next-js";
import { twoFactor } from "better-auth/plugins";

export const auth = betterAuth({
  database: prismaAdapter(prisma, {
    provider: "postgresql",
  }),
  emailAndPassword: {
    enabled: true,
  },
  session: {
    expiresIn: 60 * 60 * 24 * 7, 7 days
    updateAge: 60 * 60 * 24,
    cookieCache: {
      enabled: true,
      maxAge: 5 * 60, // use shorter time for testing
      strategy: "compact",
      refreshCache: {
        updateAge: 60,
      },
    },
  },
  plugins: [
    nextCookies(),
    twoFactor(),
  ],
});

Additional context

When disabling cookieCache entirely:

session: {
  expiresIn: 60 * 60 * 24 * 7,
  updateAge: 60 * 60 * 24,
  // cookieCache disabled
},

The TOTP verification fails with error: "invalid two factor cookie"

Probably this is what is happening:

  1. The twoFactor plugin stores pending 2FA state in session_data cookie
  2. Without cookieCache, there's no session_data to read the pending state from
  3. But with cookieCache, the refresh mechanism doesn't work after the initial authentication

Workaround (not ideal)

Setting maxAge equal to session expiresIn to avoid the refresh issue:

cookieCache: {
  enabled: true,
  maxAge: 60 * 60 * 24 * 7, // Same as session expiresIn
  strategy: "compact",
},

It is the same as not using cookieCache but prevents the logout issue.

Possible cause

The nextCookies() plugin may not be properly setting the session_data cookie when getSession() is called and only session_token exists. The refresh mechanism seems to not trigger a new cookie write.

Originally created by @Dnori96 on GitHub (Jan 25, 2026). Original GitHub issue: https://github.com/better-auth/better-auth/issues/7607 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce ### Little description of the bug When using `cookieCache` with the `twoFactor` plugin, the `session_data` cookie is not regenerated after it expires. The `session_token` cookie persists, but once `session_data` expires (after `maxAge`), the user is logged out because the session cannot be restored. Additionally, disabling `cookieCache` entirely causes TOTP verification to fail with "invalid two factor cookie" error, suggesting the `twoFactor` plugin depends on `cookieCache` to store the pending 2FA state. ### To reproduce 1. Configure better-auth with `cookieCache` and `twoFactor` plugin 2. Sign in with a user that has 2FA enabled 3. Complete TOTP verification successfully 4. User is redirected to a protected page, both `session_token` and `session_data` cookies are set 5. Wait for `cookieCache.maxAge` to expire (e.g., depending of the maxAge in auth config) 6. Refresh the page or navigate 7. `session_data` cookie disappears and is never regenerated 8. User is logged out despite `session_token` still existing ### Current vs. Expected behavior After `session_data` expires, calling `getSession()` should: 1. Validate the `session_token` against the database 2. Regenerate the `session_data` cookie with fresh cached data ### What version of Better Auth are you using? 1.4.17 ### System info ```bash "node": { "version": "v22.20.0", "env": "development" }, "packageManager": { "name": "npm", "version": "11.7.0" }, "frameworks": [ { "name": "next", "version": "^16.0.7" }, { "name": "react", "version": "^19.2.3" } ], "databases": [ { "name": "pg", "version": "^8.16.3" }, { "name": "@prisma/client", "version": "^7.3.0" } ], "betterAuth": { "version": "1.4.17", "config": null, } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth"; import { prismaAdapter } from "better-auth/adapters/prisma"; import { prisma } from "./db/prisma"; import { nextCookies } from "better-auth/next-js"; import { twoFactor } from "better-auth/plugins"; export const auth = betterAuth({ database: prismaAdapter(prisma, { provider: "postgresql", }), emailAndPassword: { enabled: true, }, session: { expiresIn: 60 * 60 * 24 * 7, 7 days updateAge: 60 * 60 * 24, cookieCache: { enabled: true, maxAge: 5 * 60, // use shorter time for testing strategy: "compact", refreshCache: { updateAge: 60, }, }, }, plugins: [ nextCookies(), twoFactor(), ], }); ``` ### Additional context **When disabling `cookieCache` entirely:** ```typescript session: { expiresIn: 60 * 60 * 24 * 7, updateAge: 60 * 60 * 24, // cookieCache disabled }, ``` The TOTP verification fails with error: `"invalid two factor cookie"` Probably this is what is happening: 1. The `twoFactor` plugin stores pending 2FA state in `session_data` cookie 2. Without `cookieCache`, there's no `session_data` to read the pending state from 3. But with `cookieCache`, the refresh mechanism doesn't work after the initial authentication ### Workaround (not ideal) Setting `maxAge` equal to session `expiresIn` to avoid the refresh issue: ```typescript cookieCache: { enabled: true, maxAge: 60 * 60 * 24 * 7, // Same as session expiresIn strategy: "compact", }, ``` It is the same as not using `cookieCache` but prevents the logout issue. ### Possible cause The `nextCookies()` plugin may not be properly setting the `session_data` cookie when `getSession()` is called and only `session_token` exists. The refresh mechanism seems to not trigger a new cookie write.
GiteaMirror added the securitycorebug labels 2026-04-15 18:37:46 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Jan 25, 2026):

It seems you've encountered an issue where the session_data cookie is not regenerating after it expires when using the twoFactor plugin alongside cookieCache. This is indeed a known problem, and there might be a few factors at play here.

The behavior you're experiencing, where the session_data cookie disappears and is not regenerated while the session_token persists, suggests that there might be an issue with how the cookies are managed during the session refresh. Specifically, it appears that after the initial expiration of the session_data, the refresh mechanism does not trigger a new cookie write if session_token is still valid.

Suggested Next Steps

  1. Check Your Implementations: Ensure that your usage of getSession() is being called correctly after the cookieCache.maxAge has expired. Double-check if there's any logic bypassing this call.

  2. Review Plugin Setup: Since disabling cookieCache leads to TOTP verification failures, it highlights the dependency of the twoFactor plugin on cookieCache for state management. It might help to keep cookieCache enabled with appropriate timing configurations.

  3. Consider Bug Reports: There have been related reports in the Better Auth ecosystem about similar cookie management issues ([reference document 1, 4](1, 4)). Consider checking out ongoing developments in those threads or possibly contributing to the issue if you have insights.

Workaround

Aligning the maxAge of the cookieCache with the session's expiresIn configuration, as mentioned in your message, seems to prevent logouts but may not be ideal for session management overall.

Feel free to test the above suggestions, and if issues persist, don't hesitate to tag members of the Better Auth team, like @bekacru or @ping__, for more concrete help.

What additional adjustments or configurations have you tried in your Better Auth setup?

If you need more help, tag @better-auth-agent in a comment so I can respond.

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3797136657 --> @better-auth-agent[bot] commented on GitHub (Jan 25, 2026): It seems you've encountered an issue where the `session_data` cookie is not regenerating after it expires when using the `twoFactor` plugin alongside `cookieCache`. This is indeed a known problem, and there might be a few factors at play here. The behavior you're experiencing, where the `session_data` cookie disappears and is not regenerated while the `session_token` persists, suggests that there might be an issue with how the cookies are managed during the session refresh. Specifically, it appears that after the initial expiration of the `session_data`, the refresh mechanism does not trigger a new cookie write if `session_token` is still valid. ### Suggested Next Steps 1. **Check Your Implementations**: Ensure that your usage of `getSession()` is being called correctly after the `cookieCache.maxAge` has expired. Double-check if there's any logic bypassing this call. 2. **Review Plugin Setup**: Since disabling `cookieCache` leads to TOTP verification failures, it highlights the dependency of the `twoFactor` plugin on `cookieCache` for state management. It might help to keep `cookieCache` enabled with appropriate timing configurations. 3. **Consider Bug Reports**: There have been related reports in the Better Auth ecosystem about similar cookie management issues ([reference document 1, 4](1, 4)). Consider checking out ongoing developments in those threads or possibly contributing to the issue if you have insights. ### Workaround Aligning the `maxAge` of the `cookieCache` with the session's `expiresIn` configuration, as mentioned in your message, seems to prevent logouts but may not be ideal for session management overall. Feel free to test the above suggestions, and if issues persist, don't hesitate to tag members of the Better Auth team, like @bekacru or @ping__, for more concrete help. What additional adjustments or configurations have you tried in your Better Auth setup? _If you need more help, tag @better-auth-agent in a comment so I can respond._ <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) • [Discord](https://discord.gg/better-auth) • [GitHub](https://github.com/better-auth/better-auth) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/better-auth) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/better-auth/better-auth)
Author
Owner

@dosubot[bot] commented on GitHub (Jan 25, 2026):

Hi @Dnori96! I'm Dosu and I’m helping the better-auth team.

This is expected behavior due to how cookieCache and session restoration work in Better Auth. The session_data cookie (which caches session and plugin state, including pending 2FA) is only refreshed or regenerated if your server code explicitly handles and forwards the set-cookie header from getSession responses. If session_data expires, but session_token is still valid, getSession will fetch the session from the database and attempt to set a new session_data cookie—but unless your integration (e.g., Next.js middleware, API route, or custom handler) forwards the set-cookie header to the client, the cookie won't be updated and the user will be logged out even though the session_token is still present. This is a documented integration detail, not a core bug (discussion, issue).

The twoFactor plugin depends on the session_data cookie to store pending 2FA state, so disabling cookieCache causes TOTP verification to fail with "invalid two factor cookie" because the required state is missing (discussion, issue).

To ensure session_data is refreshed after expiry, you need to:

  • Call getSession in a context where you can forward set-cookie headers (e.g., using asResponse: true or returnHeaders: true), and make sure your server/middleware sets those headers in the outgoing response to the client.
  • If you use Next.js, ensure your API routes or middleware forward the set-cookie header from Better Auth to the browser.
  • If you want to force a refresh, call getSession with disableCookieCache: true, but again, you must forward the set-cookie header.

The workaround of setting cookieCache.maxAge equal to session.expiresIn prevents the logout issue but disables the refresh mechanism, which is not ideal (discussion).

References for further details and code examples:

If you need a code sample for forwarding set-cookie headers in your framework, let me know your exact setup and I can provide one.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

<!-- gh-comment-id:3797142719 --> @dosubot[bot] commented on GitHub (Jan 25, 2026): <!-- Greeting --> Hi @Dnori96! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> This is expected behavior due to how cookieCache and session restoration work in Better Auth. The session_data cookie (which caches session and plugin state, including pending 2FA) is only refreshed or regenerated if your server code explicitly handles and forwards the set-cookie header from getSession responses. If session_data expires, but session_token is still valid, getSession will fetch the session from the database and attempt to set a new session_data cookie—but unless your integration (e.g., Next.js middleware, API route, or custom handler) forwards the set-cookie header to the client, the cookie won't be updated and the user will be logged out even though the session_token is still present. This is a documented integration detail, not a core bug ([discussion](https://github.com/better-auth/better-auth/discussions/4231), [issue](https://github.com/better-auth/better-auth/issues/4389)). The twoFactor plugin depends on the session_data cookie to store pending 2FA state, so disabling cookieCache causes TOTP verification to fail with "invalid two factor cookie" because the required state is missing ([discussion](https://github.com/better-auth/better-auth/discussions/6153), [issue](https://github.com/better-auth/better-auth/issues/4905)). To ensure session_data is refreshed after expiry, you need to: - Call getSession in a context where you can forward set-cookie headers (e.g., using asResponse: true or returnHeaders: true), and make sure your server/middleware sets those headers in the outgoing response to the client. - If you use Next.js, ensure your API routes or middleware forward the set-cookie header from Better Auth to the browser. - If you want to force a refresh, call getSession with disableCookieCache: true, but again, you must forward the set-cookie header. The workaround of setting cookieCache.maxAge equal to session.expiresIn prevents the logout issue but disables the refresh mechanism, which is not ideal ([discussion](https://github.com/better-auth/better-auth/discussions/6153)). References for further details and code examples: - [Session cookie renewal and integration details](https://github.com/better-auth/better-auth/discussions/4231) - [Session_data not regenerating after expiry](https://github.com/better-auth/better-auth/issues/4389) - [TwoFactor plugin and cookieCache dependency](https://github.com/better-auth/better-auth/discussions/6153) - [TOTP verification and two factor cookie](https://github.com/better-auth/better-auth/issues/4905) If you need a code sample for forwarding set-cookie headers in your framework, let me know your exact setup and I can provide one. <!-- 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/a76488d1-c0d5-4ba0-815c-021a44d0cea1?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/a76488d1-c0d5-4ba0-815c-021a44d0cea1?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/a76488d1-c0d5-4ba0-815c-021a44d0cea1?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/a76488d1-c0d5-4ba0-815c-021a44d0cea1?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/a76488d1-c0d5-4ba0-815c-021a44d0cea1?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/a76488d1-c0d5-4ba0-815c-021a44d0cea1?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/a76488d1-c0d5-4ba0-815c-021a44d0cea1?feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=This%20is%20expected%20behavior%20due%20to%20how%20cookieCache%20and%20session%20restoration%20work%20in%20Better%20Auth.%20The%20session_data%20cookie%20%28which%20caches%20session%20and%20plugin%20state%2C%20including%20pending%202FA%29%20is%20only%20refreshed%20or%20regenerated%20if%20your%20server%20code%20explicitly%20handles%20and%20forwards%20the%20set-cookie%20header%20from%20getSession%20responses.%20If%20session_data%20expires%2C%20but%20session_token%20is%20still%20valid%2C%20getSession%20will%20fetch%20the%20session%20from%20the%20database%20and%20attempt%20to%20set%20a%20new%20session_data%20cookie%E2%80%94but%20unless%20your%20integration%20%28e.g.%2C%20Next.js%20middleware%2C%20API%20route%2C%20or%20custom%20handler%29%20forwards%20the%20set-cookie%20header%20to%20the%20client%2C%20the%20cookie%20won%27t%20be%20updated%20and%20the%20user%20will%20be%20logged%20out%20even%20though%20the%20session_token%20is%20still%20present.%20This%20is%20a%20documented%20integration%20detail%2C%20not%20a%20core%20bug%20%28%5Bdiscussion%5D%28https%3A//github.com/better-auth/better-auth/discussions/4231%29%2C%20%5Bissue%5D%28https%3A//github.com/better-auth/better-auth/issues/4389%29%29.%0A%0AThe%20twoFactor%20plugin%20depends%20on%20the%20session_data%20cookie%20to%20store%20pending%202FA%20state%2C%20so%20disabling%20cookieCache%20causes%20TOTP%20verification%20to%20fail%20with%20%22invalid%20two%20factor%20cookie%22%20because%20the%20required%20state%20is%20missing%20%28%5Bdiscussion%5D%28https%3A//github.com/better-auth/better-auth/discussions/6153%29%2C%20%5Bissue%5D%28https%3A//github.com/better-auth/better-auth/issues/4905%29%29.%0A%0ATo%20ensure%20session_data%20is%20refreshed%20after%20expiry%2C%20you%20need%20to%3A%0A-%20Call%20getSession%20in%20a%20context%20where%20you%20can%20forward%20set-cookie%20headers%20%28e.g.%2C%20using%20asResponse%3A%20true%20or%20returnHeaders%3A%20true%29%2C%20and%20make%20sure%20your%20server/middleware%20sets%20those%20headers%20in%20the%20outgoing%20response%20to%20the%20client.%0A-%20If%20you%20use%20Next.js%2C%20ensure%20your%20API%20routes%20or%20middleware%20forward%20the%20set-cookie%20header%20from%20Better%20Auth%20to%20the%20browser.%0A-%20If%20you%20want%20to%20force%20a%20refresh%2C%20call%20getSession%20with%20disableCookieCache%3A%20true%2C%20but%20again%2C%20you%20must%20forward%20the%20set-cookie%20header.%0A%0AThe%20workaround%20of%20setting%20cookieCache.maxAge%20equal%20to%20session.expiresIn%20prevents%20the%20logout%20issue%20but%20disables%20the%20refresh%20mechanism%2C%20which%20is%20not%20ideal%20%28%5Bdiscussion%5D%28https%3A//github.com/better-auth/better-auth/discussions/6153%29%29.%0A%0AReferences%20for%20further%20details%20and%20code%20examples%3A%0A-%20%5BSession%20cookie%20renewal%20and%20integration%20details%5D%28https%3A//github.com/better-auth/better-auth/discussions/4231%29%0A-%20%5BSession_data%20not%20regenerating%20after%20expiry%5D%28https%3A//github.com/better-auth/better-auth/issues/4389%29%0A-%20%5BTwoFactor%20plugin%20and%20cookieCache%20dependency%5D%28https%3A//github.com/better-auth/better-auth/discussions/6153%29%0A-%20%5BTOTP%20verification%20and%20two%20factor%20cookie%5D%28https%3A//github.com/better-auth/better-auth/issues/4905%29%0A%0AIf%20you%20need%20a%20code%20sample%20for%20forwarding%20set-cookie%20headers%20in%20your%20framework%2C%20let%20me%20know%20your%20exact%20setup%20and%20I%20can%20provide%20one.)&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/7607)
Author
Owner

@Bekacru commented on GitHub (Jan 25, 2026):

Additionally, disabling cookieCache entirely causes TOTP verification to fail with "invalid two factor cookie" error, suggesting the twoFactor plugin depends on cookieCache to store the pending 2FA state.

there is no relationship between cookie cache and totp verification. unfortunately unable to reproduce this

<!-- gh-comment-id:3797200377 --> @Bekacru commented on GitHub (Jan 25, 2026): > Additionally, disabling cookieCache entirely causes TOTP verification to fail with "invalid two factor cookie" error, suggesting the twoFactor plugin depends on cookieCache to store the pending 2FA state. there is no relationship between cookie cache and totp verification. unfortunately unable to reproduce this
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#19482