[GH-ISSUE #4475] getSession or useSession not extending expiresAt #9949

Closed
opened 2026-04-13 05:47:35 -05:00 by GiteaMirror · 16 comments
Owner

Originally created by @BenLussier on GitHub (Sep 5, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/4475

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Configure session expiresIn and updateAge to short values (e.g. 4 * 60 and 1 * 60 respectively)
  2. Optional: add and configure sveltekitCookies plugin and enable session cookieCache with maxAge less than expiresIn
  3. Sign in
  4. After updateAge has passed, call useSession() a second time on client side
    OR
    Call getSession() a second time on server side

Current vs. Expected behavior

Current:

If useSession() or getSession() are called after updateAge but before expiresIn, session is not extended. (i.e. expiresAt does not change in returned object, cookie, or database)

Expected

If useSession() or getSession() are called after updateAge but before expiresIn, session is extended. (i.e. expiresAt changes to now() + expiresIn in returned object, cookie, and database)

What version of Better Auth are you using?

1.3.6

System info

{
  "system": {
    "platform": "darwin",
    "arch": "x64",
    "version": "Darwin Kernel Version 22.6.0: Wed Jul  5 22:21:56 PDT 2023; root:xnu-8796.141.3~6/RELEASE_X86_64",
    "release": "22.6.0",
    "cpuCount": 4,
    "cpuModel": "Intel(R) Core(TM) i5-8210Y CPU @ 1.60GHz",
    "totalMemory": "8.00 GB",
    "freeMemory": "0.03 GB"
  },
  "node": {
    "version": "v24.5.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "11.5.1"
  },
  "frameworks": [
    {
      "name": "svelte",
      "version": "^5.11.1"
    },
    {
      "name": "@sveltejs/kit",
      "version": "^2.28.0"
    }
  ],
  "databases": [
    {
      "name": "pg",
      "version": "^8.13.1"
    },
    {
      "name": "postgres",
      "version": "^3.4.5"
    },
    {
      "name": "drizzle",
      "version": "^0.38.1"
    }
  ],
  "betterAuth": {
    "version": "0.0.1",
    "config": {
      "appName": "BD-Admin",
      "secret": "[REDACTED]",
      "emailAndPassword": {
        "enabled": true,
        "autoSignIn": false,
        "resetPasswordTokenExpiresIn": 3600
      },
      "plugins": [
        {
          "name": "two-factor",
          "config": {
            "id": "two-factor",
            "endpoints": {},
            "options": {
              "issuer": "BD-Admin-Local",
              "otpOptions": {
                "period": 60
              },
              "totpOptions": {
                "period": 30,
                "digits": 6
              },
              "backupCodeOptions": {
                "amount": 0
              }
            },
            "hooks": {
              "after": [
                {}
              ]
            },
            "schema": {
              "user": {
                "fields": {
                  "twoFactorEnabled": {
                    "type": "boolean",
                    "required": false,
                    "defaultValue": false,
                    "input": false
                  }
                }
              },
              "twoFactor": {
                "fields": {
                  "secret": {
                    "type": "string",
                    "required": true,
                    "returned": false
                  },
                  "backupCodes": {
                    "type": "string",
                    "required": true,
                    "returned": false
                  },
                  "userId": {
                    "type": "string",
                    "required": true,
                    "returned": false,
                    "references": {
                      "model": "user",
                      "field": "id"
                    }
                  }
                }
              }
            },
            "rateLimit": [
              {
                "window": 10,
                "max": 3
              }
            ],
            "$ERROR_CODES": {
              "OTP_NOT_ENABLED": "OTP not enabled",
              "OTP_HAS_EXPIRED": "OTP has expired",
              "TOTP_NOT_ENABLED": "TOTP not enabled",
              "TWO_FACTOR_NOT_ENABLED": "Two factor isn't enabled",
              "BACKUP_CODES_NOT_ENABLED": "Backup codes aren't enabled",
              "INVALID_BACKUP_CODE": "Invalid backup code",
              "INVALID_CODE": "Invalid code",
              "TOO_MANY_ATTEMPTS_REQUEST_NEW_CODE": "Too many attempts. Please request a new code.",
              "INVALID_TWO_FACTOR_COOKIE": "Invalid two factor cookie"
            }
          }
        },
        {
          "name": "passkey",
          "config": {
            "id": "passkey",
            "endpoints": {},
            "schema": {
              "passkey": {
                "fields": {
                  "name": {
                    "type": "string",
                    "required": false
                  },
                  "publicKey": {
                    "type": "string",
                    "required": true
                  },
                  "userId": {
                    "type": "string",
                    "references": {
                      "model": "user",
                      "field": "id"
                    },
                    "required": true
                  },
                  "credentialID": {
                    "type": "string",
                    "required": true
                  },
                  "counter": {
                    "type": "number",
                    "required": true
                  },
                  "deviceType": {
                    "type": "string",
                    "required": true
                  },
                  "backedUp": {
                    "type": "boolean",
                    "required": true
                  },
                  "transports": {
                    "type": "string",
                    "required": false
                  },
                  "createdAt": {
                    "type": "date",
                    "required": false
                  },
                  "aaguid": {
                    "type": "string",
                    "required": false
                  }
                }
              }
            },
            "$ERROR_CODES": {
              "CHALLENGE_NOT_FOUND": "Challenge not found",
              "YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY": "You are not allowed to register this passkey",
              "FAILED_TO_VERIFY_REGISTRATION": "Failed to verify registration",
              "PASSKEY_NOT_FOUND": "Passkey not found",
              "AUTHENTICATION_FAILED": "Authentication failed",
              "UNABLE_TO_CREATE_SESSION": "Unable to create session",
              "FAILED_TO_UPDATE_PASSKEY": "Failed to update passkey"
            }
          }
        },
        {
          "name": "admin",
          "config": {
            "id": "admin",
            "hooks": {
              "after": [
                {}
              ]
            },
            "endpoints": {},
            "$ERROR_CODES": {
              "FAILED_TO_CREATE_USER": "Failed to create user",
              "USER_ALREADY_EXISTS": "User already exists",
              "YOU_CANNOT_BAN_YOURSELF": "You cannot ban yourself",
              "YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE": "You are not allowed to change users role",
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS": "You are not allowed to create users",
              "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS": "You are not allowed to list users",
              "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS_SESSIONS": "You are not allowed to list users sessions",
              "YOU_ARE_NOT_ALLOWED_TO_BAN_USERS": "You are not allowed to ban users",
              "YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS": "You are not allowed to impersonate users",
              "YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS": "You are not allowed to revoke users sessions",
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS": "You are not allowed to delete users",
              "YOU_ARE_NOT_ALLOWED_TO_SET_USERS_PASSWORD": "[REDACTED]",
              "BANNED_USER": "You have been banned from this application",
              "NO_DATA_TO_UPDATE": "No data to update",
              "YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS": "You are not allowed to update users"
            },
            "schema": {
              "user": {
                "fields": {
                  "role": {
                    "type": "string",
                    "required": false,
                    "input": false
                  },
                  "banned": {
                    "type": "boolean",
                    "defaultValue": false,
                    "required": false,
                    "input": false
                  },
                  "banReason": {
                    "type": "string",
                    "required": false,
                    "input": false
                  },
                  "banExpires": {
                    "type": "date",
                    "required": false,
                    "input": false
                  }
                }
              },
              "session": {
                "fields": {
                  "impersonatedBy": {
                    "type": "string",
                    "required": false
                  }
                }
              }
            },
            "options": {
              "defaultRole": "rep",
              "defaultBanReason": "Disabled"
            }
          }
        }
      ],
      "session": {
        "modelName": "bdaSessions",
        "fields": {
          "userId": "bdaUserId"
        },
        "expiresIn": 240,
        "updateAge": 60
      },
      "user": {
        "modelName": "bdaUsers"
      },
      "account": {
        "modelName": "bdaAccounts",
        "fields": {
          "userId": "bdaUserId"
        }
      },
      "verification": {
        "modelName": "bdaVerifications"
      }
    }
  }
}

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

Client

Auth config (if applicable)

NOTE: Some parts commented out to get output from CLI info to work properly (errors on .svelte extension files)

import { betterAuth } from "better-auth";
// import type { BetterAuthOptions } from "better-auth";
import { twoFactor, admin } from "better-auth/plugins";
// import { customSession } from "better-auth/plugins";
import { passkey } from "better-auth/plugins/passkey";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
// import { sveltekitCookies } from "better-auth/svelte-kit";
// import { getRequestEvent } from "$app/server";
import { db } from "./db/index";
import { env } from "$env/dynamic/private";
// import { sendResetPasswordEmail, sendOTPEmail } from "$lib/emails/index"; // your email sending function

export const auth = betterAuth({
  database: drizzleAdapter(db, {
		provider: "pg",
	}),
	appName: "BD-Admin",
	secret: env.BETTER_AUTH_SECRET,
	emailAndPassword: {  
		enabled: true,
		autoSignIn: false,
		resetPasswordTokenExpiresIn: 60 * 60, // 1 hour
		sendResetPassword: async ({user, url}) => {
      // await sendResetPasswordEmail(
			// 	user.name,
      //   user.email,
      //   url,
      // );
			return;
    },
	},
	plugins: [ 
		twoFactor({
			issuer: 'BD-Admin-Local',
			otpOptions: {
				period: 60, // seconds
				sendOTP: async ({ user, otp }) => {
					// await sendOTPEmail(
					// 	user.email,
					// 	otp,
					// );
					return;
				},
			},
			totpOptions: {
				period: 30, // seconds
				digits: 6,
			},
			backupCodeOptions: {
				amount: 0,
			}
		}),
		passkey({
			rpID: 'localhost',
			rpName: 'BD Admin',
			origin: env.PUBLIC_BETTER_AUTH_URL,
		}),
		admin({
			defaultRole: "rep",
			defaultBanReason: "Disabled"
		}),
		// sveltekitCookies(getRequestEvent) // make sure this is the last plugin in the array
	],
	session: {
		modelName: "bdaSessions",
		fields: {
			userId: "bdaUserId"
		},
		expiresIn: 4 * 60, // 15 minutes
		updateAge: 1 * 60, // every 5 mins the session expiration is updated
		// cookieCache: {
    //   enabled: true,
    //   maxAge: 1 * 60 // Cache duration in seconds
    // }
	},
	user: {
		modelName: "bdaUsers",
	},
	account: {
		modelName: "bdaAccounts",
		fields: {
			userId: "bdaUserId"
		},
	},
	verification: {
		modelName: "bdaVerifications",
	}
});

// const options = {
//   database: drizzleAdapter(db, {
// 		provider: "pg",
// 	}),
// 	appName: "BD-Admin",
// 	secret: env.BETTER_AUTH_SECRET,
// 	emailAndPassword: {  
// 		enabled: true,
// 		autoSignIn: false,
// 		resetPasswordTokenExpiresIn: 60 * 60, // 1 hour
// 		sendResetPassword: async ({user, url}) => {
//       await sendResetPasswordEmail(
// 				user.name,
//         user.email,
//         url,
//       );
//     },
// 	},
// 	plugins: [ 
// 		twoFactor({
// 			issuer: 'BD-Admin-Local',
// 			otpOptions: {
// 				period: 60, // seconds
// 				sendOTP: async ({ user, otp }) => {
// 					await sendOTPEmail(
// 						user.email,
// 						otp,
// 					);
// 				},
// 			},
// 			totpOptions: {
// 				period: 30, // seconds
// 				digits: 6,
// 			},
// 			backupCodeOptions: {
// 				amount: 0,
// 			}
// 		}),
// 		passkey({
// 			rpID: 'localhost',
// 			rpName: 'BD Admin',
// 			origin: env.PUBLIC_BETTER_AUTH_URL,
// 		}),
// 		admin({
// 			defaultRole: "rep",
// 			defaultBanReason: "Disabled"
// 		}),
// 		sveltekitCookies(getRequestEvent) // make sure this is the last plugin in the array
// 	],
// 	session: {
// 		modelName: "bdaSessions",
// 		fields: {
// 			userId: "bdaUserId"
// 		},
// 		expiresIn: 4 * 60, // 15 minutes
// 		updateAge: 1 * 60, // every 5 mins the session expiration is updated
// 		cookieCache: {
//       enabled: true,
//       maxAge: 1 * 60 // Cache duration in seconds
//     }
// 	},
// 	user: {
// 		modelName: "bdaUsers",
// 	},
// 	account: {
// 		modelName: "bdaAccounts",
// 		fields: {
// 			userId: "bdaUserId"
// 		},
// 	},
// 	verification: {
// 		modelName: "bdaVerifications",
// 	}
// } satisfies BetterAuthOptions;
 
// export const auth = betterAuth({
//     ...options,
//     plugins: [
//         ...(options.plugins ?? []),
//         customSession(async (data) => {
//             // now both user and session will infer the fields added by plugins and your custom fields
//             return data;
//         }, options), // pass options here
//     ]
// });

Additional context

I'm creating a high security app (think banking app) with short expiry time where a 'You are about to be logged out' screen pops up with countdown. I'm trying to get the 'Continue' button to extend the session and keep the user logged in.

I've tried:
Better Auth 1.0.0, 1.2.12, and 1.3.6
Calling useSession() on the client
Calling getSession() on the server with headers properly passed
With and without cookieCache enabled
With and without sveltekitCookies plugin (BA 1.3.6)
With and without customSession (#3713)
With and without disableCookieCache query parameter
I've read and understood all the Doc Concepts pages on the website
I've followed the sveltekit integration guide
Removed all plugins for potential interference

Originally created by @BenLussier on GitHub (Sep 5, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/4475 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Configure session `expiresIn` and `updateAge` to short values (e.g. 4 * 60 and 1 * 60 respectively) 2. Optional: add and configure sveltekitCookies plugin and enable session `cookieCache` with `maxAge` less than `expiresIn` 3. Sign in 4. After `updateAge` has passed, call `useSession()` a second time on client side OR Call `getSession()` a second time on server side ### Current vs. Expected behavior ### Current: If `useSession()` or `getSession()` are called after `updateAge` but before `expiresIn`, session is not extended. (i.e. `expiresAt` does not change in returned object, cookie, or database) ### Expected If `useSession()` or `getSession()` are called after `updateAge` but before `expiresIn`, session is extended. (i.e. `expiresAt` changes to `now() + expiresIn` in returned object, cookie, and database) ### What version of Better Auth are you using? 1.3.6 ### System info ```bash { "system": { "platform": "darwin", "arch": "x64", "version": "Darwin Kernel Version 22.6.0: Wed Jul 5 22:21:56 PDT 2023; root:xnu-8796.141.3~6/RELEASE_X86_64", "release": "22.6.0", "cpuCount": 4, "cpuModel": "Intel(R) Core(TM) i5-8210Y CPU @ 1.60GHz", "totalMemory": "8.00 GB", "freeMemory": "0.03 GB" }, "node": { "version": "v24.5.0", "env": "development" }, "packageManager": { "name": "npm", "version": "11.5.1" }, "frameworks": [ { "name": "svelte", "version": "^5.11.1" }, { "name": "@sveltejs/kit", "version": "^2.28.0" } ], "databases": [ { "name": "pg", "version": "^8.13.1" }, { "name": "postgres", "version": "^3.4.5" }, { "name": "drizzle", "version": "^0.38.1" } ], "betterAuth": { "version": "0.0.1", "config": { "appName": "BD-Admin", "secret": "[REDACTED]", "emailAndPassword": { "enabled": true, "autoSignIn": false, "resetPasswordTokenExpiresIn": 3600 }, "plugins": [ { "name": "two-factor", "config": { "id": "two-factor", "endpoints": {}, "options": { "issuer": "BD-Admin-Local", "otpOptions": { "period": 60 }, "totpOptions": { "period": 30, "digits": 6 }, "backupCodeOptions": { "amount": 0 } }, "hooks": { "after": [ {} ] }, "schema": { "user": { "fields": { "twoFactorEnabled": { "type": "boolean", "required": false, "defaultValue": false, "input": false } } }, "twoFactor": { "fields": { "secret": { "type": "string", "required": true, "returned": false }, "backupCodes": { "type": "string", "required": true, "returned": false }, "userId": { "type": "string", "required": true, "returned": false, "references": { "model": "user", "field": "id" } } } } }, "rateLimit": [ { "window": 10, "max": 3 } ], "$ERROR_CODES": { "OTP_NOT_ENABLED": "OTP not enabled", "OTP_HAS_EXPIRED": "OTP has expired", "TOTP_NOT_ENABLED": "TOTP not enabled", "TWO_FACTOR_NOT_ENABLED": "Two factor isn't enabled", "BACKUP_CODES_NOT_ENABLED": "Backup codes aren't enabled", "INVALID_BACKUP_CODE": "Invalid backup code", "INVALID_CODE": "Invalid code", "TOO_MANY_ATTEMPTS_REQUEST_NEW_CODE": "Too many attempts. Please request a new code.", "INVALID_TWO_FACTOR_COOKIE": "Invalid two factor cookie" } } }, { "name": "passkey", "config": { "id": "passkey", "endpoints": {}, "schema": { "passkey": { "fields": { "name": { "type": "string", "required": false }, "publicKey": { "type": "string", "required": true }, "userId": { "type": "string", "references": { "model": "user", "field": "id" }, "required": true }, "credentialID": { "type": "string", "required": true }, "counter": { "type": "number", "required": true }, "deviceType": { "type": "string", "required": true }, "backedUp": { "type": "boolean", "required": true }, "transports": { "type": "string", "required": false }, "createdAt": { "type": "date", "required": false }, "aaguid": { "type": "string", "required": false } } } }, "$ERROR_CODES": { "CHALLENGE_NOT_FOUND": "Challenge not found", "YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY": "You are not allowed to register this passkey", "FAILED_TO_VERIFY_REGISTRATION": "Failed to verify registration", "PASSKEY_NOT_FOUND": "Passkey not found", "AUTHENTICATION_FAILED": "Authentication failed", "UNABLE_TO_CREATE_SESSION": "Unable to create session", "FAILED_TO_UPDATE_PASSKEY": "Failed to update passkey" } } }, { "name": "admin", "config": { "id": "admin", "hooks": { "after": [ {} ] }, "endpoints": {}, "$ERROR_CODES": { "FAILED_TO_CREATE_USER": "Failed to create user", "USER_ALREADY_EXISTS": "User already exists", "YOU_CANNOT_BAN_YOURSELF": "You cannot ban yourself", "YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE": "You are not allowed to change users role", "YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS": "You are not allowed to create users", "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS": "You are not allowed to list users", "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS_SESSIONS": "You are not allowed to list users sessions", "YOU_ARE_NOT_ALLOWED_TO_BAN_USERS": "You are not allowed to ban users", "YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS": "You are not allowed to impersonate users", "YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS": "You are not allowed to revoke users sessions", "YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS": "You are not allowed to delete users", "YOU_ARE_NOT_ALLOWED_TO_SET_USERS_PASSWORD": "[REDACTED]", "BANNED_USER": "You have been banned from this application", "NO_DATA_TO_UPDATE": "No data to update", "YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS": "You are not allowed to update users" }, "schema": { "user": { "fields": { "role": { "type": "string", "required": false, "input": false }, "banned": { "type": "boolean", "defaultValue": false, "required": false, "input": false }, "banReason": { "type": "string", "required": false, "input": false }, "banExpires": { "type": "date", "required": false, "input": false } } }, "session": { "fields": { "impersonatedBy": { "type": "string", "required": false } } } }, "options": { "defaultRole": "rep", "defaultBanReason": "Disabled" } } } ], "session": { "modelName": "bdaSessions", "fields": { "userId": "bdaUserId" }, "expiresIn": 240, "updateAge": 60 }, "user": { "modelName": "bdaUsers" }, "account": { "modelName": "bdaAccounts", "fields": { "userId": "bdaUserId" } }, "verification": { "modelName": "bdaVerifications" } } } } ``` ### Which area(s) are affected? (Select all that apply) Client ### Auth config (if applicable) ```typescript NOTE: Some parts commented out to get output from CLI info to work properly (errors on .svelte extension files) import { betterAuth } from "better-auth"; // import type { BetterAuthOptions } from "better-auth"; import { twoFactor, admin } from "better-auth/plugins"; // import { customSession } from "better-auth/plugins"; import { passkey } from "better-auth/plugins/passkey"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; // import { sveltekitCookies } from "better-auth/svelte-kit"; // import { getRequestEvent } from "$app/server"; import { db } from "./db/index"; import { env } from "$env/dynamic/private"; // import { sendResetPasswordEmail, sendOTPEmail } from "$lib/emails/index"; // your email sending function export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg", }), appName: "BD-Admin", secret: env.BETTER_AUTH_SECRET, emailAndPassword: { enabled: true, autoSignIn: false, resetPasswordTokenExpiresIn: 60 * 60, // 1 hour sendResetPassword: async ({user, url}) => { // await sendResetPasswordEmail( // user.name, // user.email, // url, // ); return; }, }, plugins: [ twoFactor({ issuer: 'BD-Admin-Local', otpOptions: { period: 60, // seconds sendOTP: async ({ user, otp }) => { // await sendOTPEmail( // user.email, // otp, // ); return; }, }, totpOptions: { period: 30, // seconds digits: 6, }, backupCodeOptions: { amount: 0, } }), passkey({ rpID: 'localhost', rpName: 'BD Admin', origin: env.PUBLIC_BETTER_AUTH_URL, }), admin({ defaultRole: "rep", defaultBanReason: "Disabled" }), // sveltekitCookies(getRequestEvent) // make sure this is the last plugin in the array ], session: { modelName: "bdaSessions", fields: { userId: "bdaUserId" }, expiresIn: 4 * 60, // 15 minutes updateAge: 1 * 60, // every 5 mins the session expiration is updated // cookieCache: { // enabled: true, // maxAge: 1 * 60 // Cache duration in seconds // } }, user: { modelName: "bdaUsers", }, account: { modelName: "bdaAccounts", fields: { userId: "bdaUserId" }, }, verification: { modelName: "bdaVerifications", } }); // const options = { // database: drizzleAdapter(db, { // provider: "pg", // }), // appName: "BD-Admin", // secret: env.BETTER_AUTH_SECRET, // emailAndPassword: { // enabled: true, // autoSignIn: false, // resetPasswordTokenExpiresIn: 60 * 60, // 1 hour // sendResetPassword: async ({user, url}) => { // await sendResetPasswordEmail( // user.name, // user.email, // url, // ); // }, // }, // plugins: [ // twoFactor({ // issuer: 'BD-Admin-Local', // otpOptions: { // period: 60, // seconds // sendOTP: async ({ user, otp }) => { // await sendOTPEmail( // user.email, // otp, // ); // }, // }, // totpOptions: { // period: 30, // seconds // digits: 6, // }, // backupCodeOptions: { // amount: 0, // } // }), // passkey({ // rpID: 'localhost', // rpName: 'BD Admin', // origin: env.PUBLIC_BETTER_AUTH_URL, // }), // admin({ // defaultRole: "rep", // defaultBanReason: "Disabled" // }), // sveltekitCookies(getRequestEvent) // make sure this is the last plugin in the array // ], // session: { // modelName: "bdaSessions", // fields: { // userId: "bdaUserId" // }, // expiresIn: 4 * 60, // 15 minutes // updateAge: 1 * 60, // every 5 mins the session expiration is updated // cookieCache: { // enabled: true, // maxAge: 1 * 60 // Cache duration in seconds // } // }, // user: { // modelName: "bdaUsers", // }, // account: { // modelName: "bdaAccounts", // fields: { // userId: "bdaUserId" // }, // }, // verification: { // modelName: "bdaVerifications", // } // } satisfies BetterAuthOptions; // export const auth = betterAuth({ // ...options, // plugins: [ // ...(options.plugins ?? []), // customSession(async (data) => { // // now both user and session will infer the fields added by plugins and your custom fields // return data; // }, options), // pass options here // ] // }); ``` ### Additional context I'm creating a high security app (think banking app) with short expiry time where a 'You are about to be logged out' screen pops up with countdown. I'm trying to get the 'Continue' button to extend the session and keep the user logged in. I've tried: Better Auth 1.0.0, 1.2.12, and 1.3.6 Calling useSession() on the client Calling getSession() on the server with headers properly passed With and without cookieCache enabled With and without sveltekitCookies plugin (BA 1.3.6) With and without customSession ([#3713](https://github.com/better-auth/better-auth/issues/3713)) With and without disableCookieCache query parameter I've read and understood all the Doc Concepts pages on the website I've followed the sveltekit [integration guide](https://www.better-auth.com/docs/integrations/svelte-kit) Removed all plugins for potential interference
GiteaMirror added the locked label 2026-04-13 05:47:35 -05:00
Author
Owner

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

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

The session extension logic in Better Auth is governed by both your session config and how session data is retrieved. The session's expiresAt is only updated (i.e., the session is "extended") if the following are true: the session is due for an update (determined by (expiresAt - expiresIn + updateAge) <= now), and session refresh is not disabled. When this happens, expiresAt is set to now + expiresIn, and the session is updated in the database and cookie. If the session is not due for an update, or refresh is disabled, expiresAt is not extended and the existing session is returned as-is. If cookieCache is enabled and the cache is valid, the session extension logic may be bypassed entirely, returning the cached session instead of triggering an update (source).

On the server side, calling getSession does not automatically renew the session cookie unless you use the asResponse: true option and explicitly forward the set-cookie header to the client (source). This is intended behavior.

If you want to ensure the session is extended on access, try disabling cookieCache (or use the disableCookieCache query param) to force a fresh session fetch and extension logic to run. Also, make sure you are not disabling session refresh (disableRefresh). If you need the session cookie to be updated on the client after a server-side getSession, handle the response as described above.

A recent fix (PR #3578, merged 2025-07-24) ensures that session data from cookies is consistently added to the context, which could affect session renewal in some edge cases (source). If you are not on the latest version, consider upgrading.

Let me know if you need a code example for a specific integration or workaround.

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:3259904727 --> @dosubot[bot] commented on GitHub (Sep 5, 2025): <!-- Greeting --> Hi @BenLussier! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> The session extension logic in Better Auth is governed by both your session config and how session data is retrieved. The session's expiresAt is only updated (i.e., the session is "extended") if the following are true: the session is due for an update (determined by (expiresAt - expiresIn + updateAge) <= now), and session refresh is not disabled. When this happens, expiresAt is set to now + expiresIn, and the session is updated in the database and cookie. If the session is not due for an update, or refresh is disabled, expiresAt is not extended and the existing session is returned as-is. If cookieCache is enabled and the cache is valid, the session extension logic may be bypassed entirely, returning the cached session instead of triggering an update ([source](https://github.com/better-auth/better-auth/blob/a39d09d3ff83b526fb9caf691d3bbcb3b7b2335b/packages/better-auth/src/api/routes/session.ts)). On the server side, calling getSession does not automatically renew the session cookie unless you use the asResponse: true option and explicitly forward the set-cookie header to the client ([source](https://github.com/better-auth/better-auth/issues/4389)). This is intended behavior. If you want to ensure the session is extended on access, try disabling cookieCache (or use the disableCookieCache query param) to force a fresh session fetch and extension logic to run. Also, make sure you are not disabling session refresh (disableRefresh). If you need the session cookie to be updated on the client after a server-side getSession, handle the response as described above. A recent fix (PR #3578, merged 2025-07-24) ensures that session data from cookies is consistently added to the context, which could affect session renewal in some edge cases ([source](https://github.com/better-auth/better-auth/pull/3578)). If you are not on the latest version, consider upgrading. Let me know if you need a code example for a specific integration or workaround. <!-- 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/32ebc78b-0040-4493-a57e-b02a11dd66b6?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/32ebc78b-0040-4493-a57e-b02a11dd66b6?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/32ebc78b-0040-4493-a57e-b02a11dd66b6?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/32ebc78b-0040-4493-a57e-b02a11dd66b6?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/32ebc78b-0040-4493-a57e-b02a11dd66b6?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/32ebc78b-0040-4493-a57e-b02a11dd66b6?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/32ebc78b-0040-4493-a57e-b02a11dd66b6?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/4475)
Author
Owner

@dannylin108 commented on GitHub (Sep 6, 2025):

On the server side, calling getSession does not automatically renew the session cookie unless you use the asResponse: true option and explicitly forward the set-cookie header to the client (source). This is intended behavior.

I want confirmation from the team that this is intended behavior. Why in this case we need integration plugins like reactStartCookies , sveltekitCookies, etc, which are supposed to set better-auth cookies automatically from getSession , so developers may skip manually parse intended response and forward cookies.

<!-- gh-comment-id:3262093198 --> @dannylin108 commented on GitHub (Sep 6, 2025): > On the server side, calling getSession does not automatically renew the session cookie unless you use the asResponse: true option and explicitly forward the set-cookie header to the client ([source](https://github.com/better-auth/better-auth/issues/4389)). This is intended behavior. I want confirmation from the team that this is intended behavior. Why in this case we need integration plugins like `reactStartCookies` , `sveltekitCookies`, etc, which are supposed to set better-auth cookies automatically from `getSession` , so developers may skip manually parse intended response and forward cookies.
Author
Owner

@Kinfe123 commented on GitHub (Sep 6, 2025):

yeah for client-side calls through useSession() should work, but there might be caching issues, but API route calls need asResponse: true to get the set-cookie headers to forward to the client which should make sense instead of server-side call trying to modify cookie with out an explicit opt-in which is returnHeaders or asRepsonse since both can affect the repsonse object.

<!-- gh-comment-id:3262278453 --> @Kinfe123 commented on GitHub (Sep 6, 2025): yeah for client-side calls through useSession() should work, but there might be caching issues, but API route calls need asResponse: true to get the set-cookie headers to forward to the client which should make sense instead of server-side call trying to modify cookie with out an explicit opt-in which is returnHeaders or asRepsonse since both can affect the repsonse object.
Author
Owner

@Kinfe123 commented on GitHub (Sep 6, 2025):

how are you calling it ? i mean are you passing disableRefresh when calling it ?

<!-- gh-comment-id:3263093233 --> @Kinfe123 commented on GitHub (Sep 6, 2025): how are you calling it ? i mean are you passing disableRefresh when calling it ?
Author
Owner

@BenLussier commented on GitHub (Sep 8, 2025):

I'm not passing disableRefresh anywhere.
Example of how I am calling in svelte:

<script lang="ts">
import { authClient } from '$lib/auth-client';
let session = authClient.useSession();
function refreshSession() {
    session = authClient.useSession();
}
</script>

<Button onclick={refreshSession}>Refresh Session</Button>
Updated At: {$session?.data?.session?.updatedAt}
Expires At: {$session?.data?.session?.expiresAt}
<!-- gh-comment-id:3268377890 --> @BenLussier commented on GitHub (Sep 8, 2025): I'm not passing disableRefresh anywhere. Example of how I am calling in svelte: ``` <script lang="ts"> import { authClient } from '$lib/auth-client'; let session = authClient.useSession(); function refreshSession() { session = authClient.useSession(); } </script> <Button onclick={refreshSession}>Refresh Session</Button> Updated At: {$session?.data?.session?.updatedAt} Expires At: {$session?.data?.session?.expiresAt} ```
Author
Owner

@BenLussier commented on GitHub (Sep 9, 2025):

to @dannylin108 point, isn't sveltekitCookies plugin supposed to handle setting the cookies when api endpoints are hit? regardless, I am not seeing expiresAt even update in the DB when getSession() is hit in hook.server.ts (and query: {disableCookieCache: true})

<!-- gh-comment-id:3268479516 --> @BenLussier commented on GitHub (Sep 9, 2025): to @dannylin108 point, isn't sveltekitCookies plugin supposed to handle setting the cookies when api endpoints are hit? regardless, I am not seeing expiresAt even update in the DB when getSession() is hit in hook.server.ts (and query: {disableCookieCache: true})
Author
Owner

@himself65 commented on GitHub (Sep 11, 2025):

Maybe related: https://github.com/better-auth/better-auth/pull/3795

I think right now we don't revalidate when it get expired, but we will

<!-- gh-comment-id:3282755196 --> @himself65 commented on GitHub (Sep 11, 2025): Maybe related: https://github.com/better-auth/better-auth/pull/3795 I think right now we don't revalidate when it get expired, but we will
Author
Owner

@BenLussier commented on GitHub (Sep 16, 2025):

#3795

@himself65 unfortunately it is not, I am not able to revalidate BEFORE it expires

<!-- gh-comment-id:3299816828 --> @BenLussier commented on GitHub (Sep 16, 2025): > #3795 @himself65 unfortunately it is not, I am not able to revalidate BEFORE it expires
Author
Owner

@BenLussier commented on GitHub (Sep 24, 2025):

has anyone else been able to recreate this issue? I'm guessing most users don't use such short expiresAt and updateAge's and thus don't see it

<!-- gh-comment-id:3329963104 --> @BenLussier commented on GitHub (Sep 24, 2025): has anyone else been able to recreate this issue? I'm guessing most users don't use such short expiresAt and updateAge's and thus don't see it
Author
Owner

@Gonzalo-Bruna commented on GitHub (Oct 1, 2025):

has anyone else been able to recreate this issue? I'm guessing most users don't use such short expiresAt and updateAge's and thus don't see it

Yeah, it’s happening in my project too. I haven’t found a workaround yet, getting the session just doesn’t update the expiration date.

My setup looks like this:

  • A backend server with Better-Auth
  • A frontend that calls the backend endpoints directly

Since the backend and frontend are completely separate, I don’t use the Better-Auth client library. Instead, I rely on the auto-generated backend endpoints. On the frontend, every time the app opens, it calls the /get-session endpoint, but the expiredAt value never refreshes.

As a result, sessions still expire exactly one week after they’re created, and for the past month all my users have been getting logged out every week lol.

<!-- gh-comment-id:3354299102 --> @Gonzalo-Bruna commented on GitHub (Oct 1, 2025): > has anyone else been able to recreate this issue? I'm guessing most users don't use such short expiresAt and updateAge's and thus don't see it Yeah, it’s happening in my project too. I haven’t found a workaround yet, getting the session just doesn’t update the expiration date. My setup looks like this: - A backend server with Better-Auth - A frontend that calls the backend endpoints directly Since the backend and frontend are completely separate, I don’t use the Better-Auth client library. Instead, I rely on the auto-generated backend endpoints. On the frontend, every time the app opens, it calls the `/get-session` endpoint, but the expiredAt value never refreshes. As a result, sessions still expire exactly one week after they’re created, and for the past month all my users have been getting logged out every week lol.
Author
Owner

@eth0izzle commented on GitHub (Oct 30, 2025):

@Gonzalo-Bruna I also have a similar setup. Did you find a solution?

<!-- gh-comment-id:3468966240 --> @eth0izzle commented on GitHub (Oct 30, 2025): @Gonzalo-Bruna I also have a similar setup. Did you find a solution?
Author
Owner

@better-auth-agent[bot] commented on GitHub (Oct 30, 2025):

Based on what you’ve described, here are some troubleshooting steps:

• Verify that your session config’s values are correct. In your case, ensure that expiresIn is significantly larger than updateAge and that there are no overlapping behaviors from cookie caching or plugins. A minimal setup (disabling optional cookieCache and sveltekitCookies) can help isolate the issue [1].

• Double‑check that, when calling getSession/useSession after updateAge, you’re properly forwarding any headers or cookies needed to trigger the refresh. In some cases, missing or stale cookie data may prevent the session’s expiresAt from extending as expected [2].

• Try replicating the issue with a simplified configuration (without custom plugins) to see if any plugin interference is at fault. If the session extension works in a minimal config, re‑introduce plugins one at a time to determine which one may be causing the problem [1][2].

• If the issue persists on Better Auth 1.3.6 and with all configuration variations you’ve attempted, consider filing a detailed bug report (or checking for an existing one) with your full configuration and reproduction steps.

Let me know if you need further assistance.

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3468967785 --> @better-auth-agent[bot] commented on GitHub (Oct 30, 2025): Based on what you’ve described, here are some troubleshooting steps: • Verify that your session config’s values are correct. In your case, ensure that expiresIn is significantly larger than updateAge and that there are no overlapping behaviors from cookie caching or plugins. A minimal setup (disabling optional cookieCache and sveltekitCookies) can help isolate the issue [[1]](https://github.com/better-auth/better-auth/issues/4475). • Double‑check that, when calling getSession/useSession after updateAge, you’re properly forwarding any headers or cookies needed to trigger the refresh. In some cases, missing or stale cookie data may prevent the session’s expiresAt from extending as expected [[2]](https://github.com/better-auth/better-auth/issues/2115). • Try replicating the issue with a simplified configuration (without custom plugins) to see if any plugin interference is at fault. If the session extension works in a minimal config, re‑introduce plugins one at a time to determine which one may be causing the problem [[1]](https://github.com/better-auth/better-auth/issues/4475)[[2]](https://github.com/better-auth/better-auth/issues/2115). • If the issue persists on Better Auth 1.3.6 and with all configuration variations you’ve attempted, consider filing a detailed bug report (or checking for an existing one) with your full configuration and reproduction steps. Let me know if you need further assistance. <!-- 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

@BenLussier commented on GitHub (Nov 4, 2025):

@Gonzalo-Bruna no solution yet. i've been trying to get the repo working so i can troubleshoot myself and offer a fix but been having trouble getting it working on my computer.

<!-- gh-comment-id:3487839845 --> @BenLussier commented on GitHub (Nov 4, 2025): @Gonzalo-Bruna no solution yet. i've been trying to get the repo working so i can troubleshoot myself and offer a fix but been having trouble getting it working on my computer.
Author
Owner

@better-auth-agent[bot] commented on GitHub (Nov 4, 2025):

(tag @better-auth-agent)

Hi there! It looks like you're encountering an issue where calling getSession() or useSession() after the updateAge period does not extend the session's expiresAt value as expected. Based on the troubleshooting steps you've already taken, here are a few next steps to try:

  1. Double-check that your session configuration (expiresIn and updateAge) is correctly applied and that no cache (e.g., cookieCache) or customSession logic is interfering with the refresh logic. Sometimes an improperly cached session can bypass the refresh mechanism [1].

  2. Ensure that your client and server are both using the correct Better Auth version (1.3.6 in your case) and that there are no version mismatches between packages. Upgrading dependencies might help if there was a known bug that’s been fixed in a newer patch release [2].

  3. Verify that any middleware or adapters (e.g., Drizzle or SvelteKit integration) are correctly propagating the session headers and cookies, in particular if sveltekitCookies is enabled, as misconfigurations might cause the session refresh not to update expiresAt [3].

  4. Try temporarily disabling any plugins (especially custom ones like two-factor or passkey) to see if they might be interfering with the session update process. This can help narrow down whether the issue is core or plugin-related [3].

  5. Consider adding logging within the Better Auth session update logic (if possible) to confirm that the refresh is attempted and check what the new expiresAt calculation looks like. This could provide insight into whether the refresh logic is not being triggered or if it’s being overridden elsewhere.

Does this help guide your next debugging steps? If you’re still stuck, you might want to tag @bekacru and @ping__ for further assistance with your specific setup. What additional details can you share about your debugging findings?

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3487841322 --> @better-auth-agent[bot] commented on GitHub (Nov 4, 2025): (tag @better-auth-agent) Hi there! It looks like you're encountering an issue where calling getSession() or useSession() after the updateAge period does not extend the session's expiresAt value as expected. Based on the troubleshooting steps you've already taken, here are a few next steps to try: 1. Double-check that your session configuration (expiresIn and updateAge) is correctly applied and that no cache (e.g., cookieCache) or customSession logic is interfering with the refresh logic. Sometimes an improperly cached session can bypass the refresh mechanism [[1]](https://www.better-auth.com/docs/reference/options). 2. Ensure that your client and server are both using the correct Better Auth version (1.3.6 in your case) and that there are no version mismatches between packages. Upgrading dependencies might help if there was a known bug that’s been fixed in a newer patch release [[2]](https://github.com/better-auth/better-auth/issues/4125). 3. Verify that any middleware or adapters (e.g., Drizzle or SvelteKit integration) are correctly propagating the session headers and cookies, in particular if sveltekitCookies is enabled, as misconfigurations might cause the session refresh not to update expiresAt [[3]](https://www.better-auth.com/docs/basic-usage). 4. Try temporarily disabling any plugins (especially custom ones like two-factor or passkey) to see if they might be interfering with the session update process. This can help narrow down whether the issue is core or plugin-related [[3]](https://www.better-auth.com/docs/basic-usage). 5. Consider adding logging within the Better Auth session update logic (if possible) to confirm that the refresh is attempted and check what the new expiresAt calculation looks like. This could provide insight into whether the refresh logic is not being triggered or if it’s being overridden elsewhere. Does this help guide your next debugging steps? If you’re still stuck, you might want to tag @bekacru and @ping__ for further assistance with your specific setup. What additional details can you share about your debugging findings? <!-- 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

@Gonzalo-Bruna commented on GitHub (Nov 4, 2025):

@Gonzalo-Bruna I also have a similar setup. Did you find a solution?

@Gonzalo-Bruna no solution yet. i've been trying to get the repo working so i can troubleshoot myself and offer a fix but been having trouble getting it working on my computer.

Sadly I couldn't find a solution yet either. So meanwhile I had to literally set the sessions to last 1 year so people is not logged out of the app. If anybody finds a solution, that would be very helpful.

<!-- gh-comment-id:3488438066 --> @Gonzalo-Bruna commented on GitHub (Nov 4, 2025): > [@Gonzalo-Bruna](https://github.com/Gonzalo-Bruna) I also have a similar setup. Did you find a solution? > [@Gonzalo-Bruna](https://github.com/Gonzalo-Bruna) no solution yet. i've been trying to get the repo working so i can troubleshoot myself and offer a fix but been having trouble getting it working on my computer. Sadly I couldn't find a solution yet either. So meanwhile I had to literally set the sessions to last 1 year so people is not logged out of the app. If anybody finds a solution, that would be very helpful.
Author
Owner

@dosubot[bot] commented on GitHub (Feb 3, 2026):

Hi, @BenLussier. I'm Dosu, and I'm helping the better-auth team manage their backlog and am marking this issue as stale.

Issue Summary:

  • You reported that in Better Auth v1.3.6, calling getSession() or useSession() after updateAge but before expiresIn does not extend the session's expiresAt as expected.
  • It was clarified that session extension only happens if the session is due for an update and refresh is enabled.
  • Server-side getSession requires the asResponse: true option to renew cookies, which is intended behavior.
  • Other users confirmed the issue in similar setups, with no current workaround besides extending session durations.
  • Maintainers recommended verifying configuration, disabling cookie caching, ensuring proper header forwarding, and testing with minimal setups.

Next Steps:

  • Please confirm if this issue is still relevant with the latest version of better-auth, and if so, you can keep the discussion open by commenting here.
  • Otherwise, I will automatically close this issue in 7 days.

Thanks for your understanding and contribution!

<!-- gh-comment-id:3842248229 --> @dosubot[bot] commented on GitHub (Feb 3, 2026): Hi, @BenLussier. I'm [Dosu](https://dosu.dev), and I'm helping the better-auth team manage their backlog and am marking this issue as stale. **Issue Summary:** - You reported that in Better Auth v1.3.6, calling getSession() or useSession() after updateAge but before expiresIn does not extend the session's expiresAt as expected. - It was clarified that session extension only happens if the session is due for an update and refresh is enabled. - Server-side getSession requires the `asResponse: true` option to renew cookies, which is intended behavior. - Other users confirmed the issue in similar setups, with no current workaround besides extending session durations. - Maintainers recommended verifying configuration, disabling cookie caching, ensuring proper header forwarding, and testing with minimal setups. **Next Steps:** - Please confirm if this issue is still relevant with the latest version of better-auth, and if so, you can keep the discussion open by commenting here. - Otherwise, I will automatically close this issue in 7 days. Thanks for your understanding and contribution!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#9949