[GH-ISSUE #2390] Update user: Only currently active session is updated when secondary storage is enabled #9177

Closed
opened 2026-04-13 04:32:41 -05:00 by GiteaMirror · 9 comments
Owner

Originally created by @Suu-ly on GitHub (Apr 21, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/2390

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Enable secondary storage
  2. Log in on two separate instances to create two different sessions
  3. Update user on one session
  4. User data on other session is outdated

Current vs. Expected behavior

Current:
Updating the user only updates the user data for currently active session when secondary storage is enabled, resulting in stale data for other sessions

Expected:
User data is updated across all sessions

What version of Better Auth are you using?

1.2.6

Provide environment information

NA

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

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth"
export const auth = betterAuth({
  secondaryStorage: {
    get: async (key) => {
      const value = await redis.get<string>(key);
      return value ? value : null;
    },
    set: async (key, value, ttl) => {
      if (ttl) await redis.set(key, value, { ex: ttl });
      else await redis.set(key, value);
    },
    delete: async (key) => {
      await redis.del(key);
    },
  },
});

Additional context

No response

Originally created by @Suu-ly on GitHub (Apr 21, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/2390 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Enable secondary storage 2. Log in on two separate instances to create two different sessions 3. Update user on one session 4. User data on other session is outdated ### Current vs. Expected behavior Current: Updating the user only updates the user data for currently active session when secondary storage is enabled, resulting in stale data for other sessions Expected: User data is updated across all sessions ### What version of Better Auth are you using? 1.2.6 ### Provide environment information ```bash NA ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ secondaryStorage: { get: async (key) => { const value = await redis.get<string>(key); return value ? value : null; }, set: async (key, value, ttl) => { if (ttl) await redis.set(key, value, { ex: ttl }); else await redis.set(key, value); }, delete: async (key) => { await redis.del(key); }, }, }); ``` ### Additional context _No response_
GiteaMirror added the locked label 2026-04-13 04:32:41 -05:00
Author
Owner

@Prashant-S29 commented on GitHub (May 17, 2025):

hey @Suu-ly, I am not a maintainer but can you pls share the code where you are updating the session

<!-- gh-comment-id:2888272260 --> @Prashant-S29 commented on GitHub (May 17, 2025): hey @Suu-ly, I am not a maintainer but can you pls share the code where you are updating the session
Author
Owner

@Suu-ly commented on GitHub (May 17, 2025):

@Prashant-S29

I'm updating the user on the server side as follows:

const res = await auth.api.updateUser({
    headers: await headers(),
    body: {
      name: newName,
    },
  });

I have moved back to storing session on the database again.

<!-- gh-comment-id:2888280687 --> @Suu-ly commented on GitHub (May 17, 2025): @Prashant-S29 I'm updating the user on the server side as follows: ```JavaScript const res = await auth.api.updateUser({ headers: await headers(), body: { name: newName, }, }); ``` I have moved back to storing session on the database again.
Author
Owner

@Prashant-S29 commented on GitHub (May 17, 2025):

can you also share the auth config (auth.ts)

<!-- gh-comment-id:2888303085 --> @Prashant-S29 commented on GitHub (May 17, 2025): can you also share the auth config (auth.ts)
Author
Owner

@Suu-ly commented on GitHub (May 17, 2025):

The relevant portions are in the original issue above

<!-- gh-comment-id:2888330960 --> @Suu-ly commented on GitHub (May 17, 2025): The relevant portions are in the original issue above
Author
Owner

@FlawaCLV commented on GitHub (May 19, 2025):

Same issue.

<!-- gh-comment-id:2891601490 --> @FlawaCLV commented on GitHub (May 19, 2025): Same issue.
Author
Owner

@Kinfe123 commented on GitHub (Jun 12, 2025):

can you try this -

npm i https://pkg.pr.new/better-auth/better-auth@16518a8

and let me know if this actually fixes your issue

<!-- gh-comment-id:2968179253 --> @Kinfe123 commented on GitHub (Jun 12, 2025): can you try this - ```bash npm i https://pkg.pr.new/better-auth/better-auth@16518a8 ``` and let me know if this actually fixes your issue
Author
Owner

@michalpuchmertl commented on GitHub (Jun 13, 2025):

I've got the same issue as well. I've tried to update the package as @Kinfe123 advised but that didn't help. Only session (session 1) from active tab is updated in Redis. Session in other tab (session 2) that is bound to same account as session 1 doesn't get updated after calling auth.api.updateUser()

Redis records

Session 1 (updated correctly)
key: BKPat6Ftpr9zDbjAeL1RxCBmvKrQb2yq

{
  "user": {
    "name": "",
    "email": "example@example.com",
    "emailVerified": true,
    "image": null,
    "createdAt": "2025-05-30T22:49:52.519Z",
    "updatedAt": "2025-05-30T22:49:52.519Z",
    "stripeCustomerId": "cus_SPQbzrWYM5LDs1",
    "firstName": "John", <------ CHANGED VALUE
    "lastName": "Doe",
    "telephoneNumber": "+420712345678",
    "notificationEmail": "example@example.com",
    "id": "bxJqGPRcoVD1NfMvURuNh3puogE6ffkT"
  },
  "session": {
    "ipAddress": "",
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
    "expiresAt": "2025-06-20T00:57:34.686Z",
    "userId": "bxJqGPRcoVD1NfMvURuNh3puogE6ffkT",
    "token": "BKPat6Ftpr9zDbjAeL1RxCBmvKrQb2yq",
    "createdAt": "2025-06-13T00:57:34.686Z",
    "updatedAt": "2025-06-13T00:57:34.686Z"
  }
}

Session 2 (stale)
key: RZboxprlkCpkwEuI14BFMl09zUSBFPIv

{
  "user": {
    "name": "",
    "email": "example@example.com",
    "emailVerified": true,
    "image": null,
    "createdAt": "2025-05-30T22:49:52.519Z",
    "updatedAt": "2025-05-30T22:49:52.519Z",
    "stripeCustomerId": "cus_SPQbzrWYM5LDs1",
    "firstName": "Jane", <------ STALE VALUE, EXPECTED "John"
    "lastName": "Doe",
    "telephoneNumber": "+420712345678",
    "notificationEmail": "example@example.com",
    "id": "bxJqGPRcoVD1NfMvURuNh3puogE6ffkT"
  },
  "session": {
    "ipAddress": "",
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
    "expiresAt": "2025-06-20T00:57:38.751Z",
    "userId": "bxJqGPRcoVD1NfMvURuNh3puogE6ffkT",
    "token": "RZboxprlkCpkwEuI14BFMl09zUSBFPIv",
    "createdAt": "2025-06-13T00:57:38.751Z",
    "updatedAt": "2025-06-13T00:57:38.751Z"
  }
}

active sessions record
key: active-sessions-bxJqGPRcoVD1NfMvURuNh3puogE6ffkT

[
  {
    "token": "BKPat6Ftpr9zDbjAeL1RxCBmvKrQb2yq",
    "expiresAt": 1750381054970
  },
  {
    "token": "RZboxprlkCpkwEuI14BFMl09zUSBFPIv",
    "expiresAt": 1750381058798
  }
]

auth.ts

export const auth = betterAuth({
  baseURL: getVercelHostname(),
  database: drizzleAdapter(db, {
    provider: "pg",
    schema,
  }),

  trustedOrigins: [...],
  rateLimit: {
    enabled: true,
    storage: "secondary-storage",
  },
  session: {
    cookieCache: {
      enabled: true,
      maxAge: 60 * 5,
    },
  },
  secondaryStorage: {
    get: async (key) => {
      try {
        const value = await redis.get(key);
        if (value === null) return null;
        return typeof value === "string" ? value : JSON.stringify(value);
      } catch (error) {
        console.error("Failed to get from Redis:", error);
        return null;
      }
    },
    set: async (key, value, ttl) => {
      try {
        JSON.parse(value);
        const options = ttl ? { ex: ttl } : undefined;
        await redis.set(key, value, options);
      } catch (error) {
        console.error("Fauked to set in Redis:", error);
      }
    },
    delete: async (key) => {
      try {
        await redis.del(key);
      } catch (error) {
        console.log("Failed to delete from Redis:", error);
      }
    },
  },
  account: {
    accountLinking: {
      enabled: true,
      allowDifferentEmails: false,
      trustedProviders: ["google", "seznam"],
    },
  },
  user: {
    changeEmail: {
      enabled: false,
    },
    additionalFields: {
      firstName: {
        type: "string",
        required: false,
        input: true,
      },
      lastName: {
        type: "string",
        required: false,
        input: true,
      },
      telephoneNumber: {
        type: "string",
        required: false,
        input: true,
      },
      notificationEmail: {
        type: "string",
        required: false,
        input: true,
      },
    },
  },
  databaseHooks: {
    user: {
      create: {
        before: async (user) => {
          return {
            data: {
              ...user,
              notificationEmail: user.email ?? null,
            },
          };
        },
      },
    },
  },
  emailAndPassword: {
    enabled: true,
    autoSignIn: true,
    sendResetPassword: async ({ user, url }) => {
      await sendResetPasswordEmail({
        email: user.email,
        name: user.name,
        url,
      });
    },
    // * This allows entering any password to sign in if NODE_ENV is not production
    ...(process.env.NODE_ENV !== "production" && {
      password: {
        verify: async ({ password }) => {
          // In non-production environments, always return true (bypass password verification)
          if (password === "invalid") return false;
          return true;
        },
      },
    }),
  },

  emailVerification: {
    sendVerificationEmail: async ({ user, url }) => {
      await sendVerificationEmail({
        email: user.email,
        name: user.name,
        url,
      });
    },
    autoSignInAfterVerification: true,
    sendOnSignUp: true,
  },
  disabledPaths: ["/send-verification-email"],
  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_OAUTH_CLIENT_ID as string,
      clientSecret: process.env.GOOGLE_OAUTH_CLIENT_SECRET as string,
      mapProfileToUser: async (profile) => {
        return {
          firstName: profile.given_name,
          lastName: profile.family_name,
          email: profile.email,
          emailVerified: profile.email_verified,
          name: profile.name,
          image: profile.picture,
        };
      },
    },
  },
  plugins: [
    stripe({
      stripeClient,
      stripeWebhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
      createCustomerOnSignUp: true,
      subscription: {
        enabled: true,
        plans: async () => {
          const plans = await stripeClient.prices.list({
            active: true,
            product: process.env.STRIPE_PRODUCT_ID,
          });

          return plans.data.map((price) => {
            return {
              name: price.id,
              priceId: price.id,
            };
          });
        },
        onSubscriptionComplete: async ({ subscription }) => {
          if (!subscription.stripeCustomerId) {
            return;
          }

          const user = await db.query.user.findFirst({
            where: eq(
              schema.user.stripeCustomerId,
              subscription.stripeCustomerId,
            ),
          });

          if (!user || !user.email) {
            return;
          }
        },
      },
    }),
    nextCookies(),
  ],
});

export const getCachedSession = cache(async () => {
  const session = await auth.api.getSession({
    headers: await headers(),
  });

  return session;
});

export const isUserSubscribed = cache(async () => {
  const subscriptions = await auth.api.listActiveSubscriptions({
    headers: await headers(),
  });

  return subscriptions.length > 0;
});

I'm moving back to storing session in relational database and will check for updates on this thread.

<!-- gh-comment-id:2968685171 --> @michalpuchmertl commented on GitHub (Jun 13, 2025): I've got the same issue as well. I've tried to update the package as @Kinfe123 advised but that didn't help. Only session (session 1) from active tab is updated in Redis. Session in other tab (session 2) that is bound to same account as session 1 doesn't get updated after calling `auth.api.updateUser()` ### Redis records **Session 1 (updated correctly)** `key: BKPat6Ftpr9zDbjAeL1RxCBmvKrQb2yq ` ```json { "user": { "name": "", "email": "example@example.com", "emailVerified": true, "image": null, "createdAt": "2025-05-30T22:49:52.519Z", "updatedAt": "2025-05-30T22:49:52.519Z", "stripeCustomerId": "cus_SPQbzrWYM5LDs1", "firstName": "John", <------ CHANGED VALUE "lastName": "Doe", "telephoneNumber": "+420712345678", "notificationEmail": "example@example.com", "id": "bxJqGPRcoVD1NfMvURuNh3puogE6ffkT" }, "session": { "ipAddress": "", "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36", "expiresAt": "2025-06-20T00:57:34.686Z", "userId": "bxJqGPRcoVD1NfMvURuNh3puogE6ffkT", "token": "BKPat6Ftpr9zDbjAeL1RxCBmvKrQb2yq", "createdAt": "2025-06-13T00:57:34.686Z", "updatedAt": "2025-06-13T00:57:34.686Z" } } ``` **Session 2 (stale)** `key: RZboxprlkCpkwEuI14BFMl09zUSBFPIv` ```json { "user": { "name": "", "email": "example@example.com", "emailVerified": true, "image": null, "createdAt": "2025-05-30T22:49:52.519Z", "updatedAt": "2025-05-30T22:49:52.519Z", "stripeCustomerId": "cus_SPQbzrWYM5LDs1", "firstName": "Jane", <------ STALE VALUE, EXPECTED "John" "lastName": "Doe", "telephoneNumber": "+420712345678", "notificationEmail": "example@example.com", "id": "bxJqGPRcoVD1NfMvURuNh3puogE6ffkT" }, "session": { "ipAddress": "", "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36", "expiresAt": "2025-06-20T00:57:38.751Z", "userId": "bxJqGPRcoVD1NfMvURuNh3puogE6ffkT", "token": "RZboxprlkCpkwEuI14BFMl09zUSBFPIv", "createdAt": "2025-06-13T00:57:38.751Z", "updatedAt": "2025-06-13T00:57:38.751Z" } } ``` **active sessions record** `key: active-sessions-bxJqGPRcoVD1NfMvURuNh3puogE6ffkT` ```json [ { "token": "BKPat6Ftpr9zDbjAeL1RxCBmvKrQb2yq", "expiresAt": 1750381054970 }, { "token": "RZboxprlkCpkwEuI14BFMl09zUSBFPIv", "expiresAt": 1750381058798 } ] ``` ### `auth.ts` ```typescript export const auth = betterAuth({ baseURL: getVercelHostname(), database: drizzleAdapter(db, { provider: "pg", schema, }), trustedOrigins: [...], rateLimit: { enabled: true, storage: "secondary-storage", }, session: { cookieCache: { enabled: true, maxAge: 60 * 5, }, }, secondaryStorage: { get: async (key) => { try { const value = await redis.get(key); if (value === null) return null; return typeof value === "string" ? value : JSON.stringify(value); } catch (error) { console.error("Failed to get from Redis:", error); return null; } }, set: async (key, value, ttl) => { try { JSON.parse(value); const options = ttl ? { ex: ttl } : undefined; await redis.set(key, value, options); } catch (error) { console.error("Fauked to set in Redis:", error); } }, delete: async (key) => { try { await redis.del(key); } catch (error) { console.log("Failed to delete from Redis:", error); } }, }, account: { accountLinking: { enabled: true, allowDifferentEmails: false, trustedProviders: ["google", "seznam"], }, }, user: { changeEmail: { enabled: false, }, additionalFields: { firstName: { type: "string", required: false, input: true, }, lastName: { type: "string", required: false, input: true, }, telephoneNumber: { type: "string", required: false, input: true, }, notificationEmail: { type: "string", required: false, input: true, }, }, }, databaseHooks: { user: { create: { before: async (user) => { return { data: { ...user, notificationEmail: user.email ?? null, }, }; }, }, }, }, emailAndPassword: { enabled: true, autoSignIn: true, sendResetPassword: async ({ user, url }) => { await sendResetPasswordEmail({ email: user.email, name: user.name, url, }); }, // * This allows entering any password to sign in if NODE_ENV is not production ...(process.env.NODE_ENV !== "production" && { password: { verify: async ({ password }) => { // In non-production environments, always return true (bypass password verification) if (password === "invalid") return false; return true; }, }, }), }, emailVerification: { sendVerificationEmail: async ({ user, url }) => { await sendVerificationEmail({ email: user.email, name: user.name, url, }); }, autoSignInAfterVerification: true, sendOnSignUp: true, }, disabledPaths: ["/send-verification-email"], socialProviders: { google: { clientId: process.env.GOOGLE_OAUTH_CLIENT_ID as string, clientSecret: process.env.GOOGLE_OAUTH_CLIENT_SECRET as string, mapProfileToUser: async (profile) => { return { firstName: profile.given_name, lastName: profile.family_name, email: profile.email, emailVerified: profile.email_verified, name: profile.name, image: profile.picture, }; }, }, }, plugins: [ stripe({ stripeClient, stripeWebhookSecret: process.env.STRIPE_WEBHOOK_SECRET!, createCustomerOnSignUp: true, subscription: { enabled: true, plans: async () => { const plans = await stripeClient.prices.list({ active: true, product: process.env.STRIPE_PRODUCT_ID, }); return plans.data.map((price) => { return { name: price.id, priceId: price.id, }; }); }, onSubscriptionComplete: async ({ subscription }) => { if (!subscription.stripeCustomerId) { return; } const user = await db.query.user.findFirst({ where: eq( schema.user.stripeCustomerId, subscription.stripeCustomerId, ), }); if (!user || !user.email) { return; } }, }, }), nextCookies(), ], }); export const getCachedSession = cache(async () => { const session = await auth.api.getSession({ headers: await headers(), }); return session; }); export const isUserSubscribed = cache(async () => { const subscriptions = await auth.api.listActiveSubscriptions({ headers: await headers(), }); return subscriptions.length > 0; }); ``` I'm moving back to storing session in relational database and will check for updates on this thread.
Author
Owner

@Kinfe123 commented on GitHub (Jun 13, 2025):

oh i mean this one -

npm i https://pkg.pr.new/better-auth/better-auth@3000

that one is by mistake

<!-- gh-comment-id:2968741135 --> @Kinfe123 commented on GitHub (Jun 13, 2025): oh i mean this one - ``` npm i https://pkg.pr.new/better-auth/better-auth@3000 ``` that one is by mistake
Author
Owner

@Suu-ly commented on GitHub (Jun 14, 2025):

The fix works for me! Thanks for working on this

<!-- gh-comment-id:2972137306 --> @Suu-ly commented on GitHub (Jun 14, 2025): The fix works for me! Thanks for working on 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#9177