mongoose user model data update not reflected in user data inside retrieved session from auth.api.getSession #886

Closed
opened 2026-03-13 08:08:52 -05:00 by GiteaMirror · 6 comments
Owner

Originally created by @prasanjit101 on GitHub (Mar 20, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. create a mongoose connection
import { env } from '@/env';
import mongoose from 'mongoose';

const MONGODB_URI = env.MONGODB_URI;

if (!MONGODB_URI) {
	throw new Error('Please define the MONGODB_URI environment variable');
}

let cached = (global as any).mongoose;

if (!cached) {
	cached = (global as any).mongoose = { conn: null, promise: null };
}

async function connectDB() {
	if (cached.conn) {
		return cached.conn;
	}

	if (!cached.promise) {
		const opts = {
			bufferCommands: false,
		};

		cached.promise = await mongoose.connect(process.env.MONGODB_URI!, opts);
		
	}
	cached.conn = await cached.promise;
	return cached.conn;
}

export default connectDB;
  1. create a different mongo client for better-auth mongo adapter
iexport const auth = betterAuth({
    database: mongodbAdapter(db),
    user: {
        additionalFields: {
            show: {
                type: "string",
                required: false,
                defaultValue: "onboard",
                input: false, // don't allow user to set role
            }
        },
    },
    session: {
        additionalFields: {
            jwt: {
                type: "string",
                required: false,
                defaultValue: "",
            }
        }
    },
    plugins: [
        nextCookies(),
        multiSession({
            maximumSessions: 2,
        }),
        organization({
        //    ...
        }),
    ],
});
  1. Add a field "show" to the user schema and also to the session as in above
  2. Now inside of a server function we update the user field show with mongoose model
await connectDB();
await User.updateOne({
            userId: session.user.id,
        }, userUpdate);
  1. Retrieve a session using
import { cache } from "react";

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

Current vs. Expected behavior

Current

After the session is updated using the mongoose model. The session data is not updated.

more details -
As in this case I was trying to update the show field in the user using the user mongoose model. But after the data is updated in the database which I confirmed from inside of mongo atlas.
I can see that the session is still not retrieving the correct data.
And it happens even if I delete the database, the getSession function continues to fetch the same old data which doesn't exist. As if it is looking up this values from some other sources and not the database.

Expected

When I update the user field using the mongoose module, the retrieved session should also fetch the updated data.

What version of Better Auth are you using?

^1.2.4

Provide environment information

- OS: Mac Os Sequoia 15.3.2
- Browser: MS Edge

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

Backend, Client

Auth config (if applicable)

export const auth = betterAuth({
    database: mongodbAdapter(db),
    rateLimit: {
        storage: "secondary-storage",
    },
    secondaryStorage: {
        get: async (key) => {
            try {
                return await withRedis(async (client) => {
                    const value = await client.get(key);
                    return value ? value : null;
                });
            } catch (error) {
                console.error(`[Redis Secondary Storage] Error getting key ${key}`, error);
                return null;
            }
        },
        set: async (key, value, ttl) => {
            try {
                await withRedis(async (client) => {
                    if (ttl) await client.set(key, value, 'EX', ttl);
                    else await client.set(key, value);
                });
            } catch (error) {
                console.error(`[Redis Secondary Storage] Error setting key ${key}`, error);
            }
        },
        delete: async (key) => {
            try {
                await withRedis(async (client) => {
                    await client.del(key);
                });
            } catch (error) {
                console.error(`[Redis Secondary Storage] Error deleting key ${key}`, error);
            }
        }
    },
    databaseHooks: {
        session: {
            create: {
                before: async (session, ctx) => {
                    // Modify the user object before it is created
                    return {
                        data: {
                            ...session,
                            jwt: await new SignJWT({
                                userId: session.userId,
                                sessionId: session.id,
                                expiresAt: session.expiresAt,
                            })
                                .setProtectedHeader({ alg: 'HS256' })
                                .sign(new TextEncoder().encode(env.BETTER_AUTH_SECRET)),
                        },
                    };
                },
            },
        },
        user: {
            create: {
                async after(user, context) {
                    if (env.NODE_ENV === 'production') {
                        await sendWelcomeEmail({
                            name: user.name,
                            emails: [user.email],
                            body: `You just signed up for growth`,
                        });
                    }
                },
            },
        }
    },
    user: {
        additionalFields: {
            show: {
                type: "string",
                required: false,
                defaultValue: "",
                input: false, // don't allow user to set role
            }
        },
    },
    session: {
        additionalFields: {
            jwt: {
                type: "string",
                required: false,
                defaultValue: "",
            }
        }
    },
    socialProviders: {
        google: {
            clientId: env.GOOGLE_CLIENT_ID,
            clientSecret: env.GOOGLE_CLIENT_SECRET,
        },
    },
    plugins: [
        nextCookies(),
        multiSession({
            maximumSessions: 2,
        }),
        organization({
            creatorRole: "owner",
            allowUserToCreateOrganization: true,
            invitationExpiresIn: 60 * 60 * 24 * 2, // 2 days
            organizationLimit: 1,
            organizationCreation: {
                afterCreate: async ({ organization, member, user }, request) => {
                    // create default resources, send notifications
                    await setupDefaultResources({ organization, member, user } as any)
                }
            },
            async sendInvitationEmail(data) {
                const inviteLink = `${env.NEXT_PUBLIC_APP_URL}/accept-invitation/${data.invitation.id}`
                sendInvitationEmail({
                    role: data.invitation.role,
                    email: data.invitation.email,
                    organizationId: data.organization.id,
                    organizationName: data.organization.name,
                    expiresAt: data.invitation.expiresAt,
                    invitedByUsername: data.inviter.user.name,
                    inviteLink: inviteLink
                })
            },
        }),
        admin({
            adminRoles: ["system-admin"],
            defaultBanReason: "Abuse of system",
            bannedUserMessage: "You have been banned from the system. Please contact support if you believe there has been a mistake.",
        })
    ],
});

Additional context

No response

Originally created by @prasanjit101 on GitHub (Mar 20, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. create a mongoose connection ``` import { env } from '@/env'; import mongoose from 'mongoose'; const MONGODB_URI = env.MONGODB_URI; if (!MONGODB_URI) { throw new Error('Please define the MONGODB_URI environment variable'); } let cached = (global as any).mongoose; if (!cached) { cached = (global as any).mongoose = { conn: null, promise: null }; } async function connectDB() { if (cached.conn) { return cached.conn; } if (!cached.promise) { const opts = { bufferCommands: false, }; cached.promise = await mongoose.connect(process.env.MONGODB_URI!, opts); } cached.conn = await cached.promise; return cached.conn; } export default connectDB; ``` 2. create a different mongo client for better-auth mongo adapter ``` iexport const auth = betterAuth({ database: mongodbAdapter(db), user: { additionalFields: { show: { type: "string", required: false, defaultValue: "onboard", input: false, // don't allow user to set role } }, }, session: { additionalFields: { jwt: { type: "string", required: false, defaultValue: "", } } }, plugins: [ nextCookies(), multiSession({ maximumSessions: 2, }), organization({ // ... }), ], }); ``` 3. Add a field "show" to the user schema and also to the session as in above 4. Now inside of a server function we update the user field `show` with mongoose model ``` await connectDB(); await User.updateOne({ userId: session.user.id, }, userUpdate); ``` 5. Retrieve a session using ``` import { cache } from "react"; export const getSession = cache(async () => { const session = await auth.api.getSession({ headers: await headers() }); return session; }); ``` ### Current vs. Expected behavior ### Current After the session is updated using the mongoose model. The session data is not updated. more details - As in this case I was trying to update the `show` field in the user using the user mongoose model. But after the data is updated in the database which I confirmed from inside of mongo atlas. I can see that the session is still not retrieving the correct data. And it happens even if I delete the database, the getSession function continues to fetch the same old data which doesn't exist. As if it is looking up this values from some other sources and not the database. ### Expected When I update the user field using the mongoose module, the retrieved session should also fetch the updated data. ### What version of Better Auth are you using? ^1.2.4 ### Provide environment information ```bash - OS: Mac Os Sequoia 15.3.2 - Browser: MS Edge ``` ### Which area(s) are affected? (Select all that apply) Backend, Client ### Auth config (if applicable) ```typescript export const auth = betterAuth({ database: mongodbAdapter(db), rateLimit: { storage: "secondary-storage", }, secondaryStorage: { get: async (key) => { try { return await withRedis(async (client) => { const value = await client.get(key); return value ? value : null; }); } catch (error) { console.error(`[Redis Secondary Storage] Error getting key ${key}`, error); return null; } }, set: async (key, value, ttl) => { try { await withRedis(async (client) => { if (ttl) await client.set(key, value, 'EX', ttl); else await client.set(key, value); }); } catch (error) { console.error(`[Redis Secondary Storage] Error setting key ${key}`, error); } }, delete: async (key) => { try { await withRedis(async (client) => { await client.del(key); }); } catch (error) { console.error(`[Redis Secondary Storage] Error deleting key ${key}`, error); } } }, databaseHooks: { session: { create: { before: async (session, ctx) => { // Modify the user object before it is created return { data: { ...session, jwt: await new SignJWT({ userId: session.userId, sessionId: session.id, expiresAt: session.expiresAt, }) .setProtectedHeader({ alg: 'HS256' }) .sign(new TextEncoder().encode(env.BETTER_AUTH_SECRET)), }, }; }, }, }, user: { create: { async after(user, context) { if (env.NODE_ENV === 'production') { await sendWelcomeEmail({ name: user.name, emails: [user.email], body: `You just signed up for growth`, }); } }, }, } }, user: { additionalFields: { show: { type: "string", required: false, defaultValue: "", input: false, // don't allow user to set role } }, }, session: { additionalFields: { jwt: { type: "string", required: false, defaultValue: "", } } }, socialProviders: { google: { clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET, }, }, plugins: [ nextCookies(), multiSession({ maximumSessions: 2, }), organization({ creatorRole: "owner", allowUserToCreateOrganization: true, invitationExpiresIn: 60 * 60 * 24 * 2, // 2 days organizationLimit: 1, organizationCreation: { afterCreate: async ({ organization, member, user }, request) => { // create default resources, send notifications await setupDefaultResources({ organization, member, user } as any) } }, async sendInvitationEmail(data) { const inviteLink = `${env.NEXT_PUBLIC_APP_URL}/accept-invitation/${data.invitation.id}` sendInvitationEmail({ role: data.invitation.role, email: data.invitation.email, organizationId: data.organization.id, organizationName: data.organization.name, expiresAt: data.invitation.expiresAt, invitedByUsername: data.inviter.user.name, inviteLink: inviteLink }) }, }), admin({ adminRoles: ["system-admin"], defaultBanReason: "Abuse of system", bannedUserMessage: "You have been banned from the system. Please contact support if you believe there has been a mistake.", }) ], }); ``` ### Additional context _No response_
GiteaMirror added the bug label 2026-03-13 08:08:52 -05:00
Author
Owner

@prasanjit101 commented on GitHub (Mar 21, 2025):

additional point: This bug persists even if I refresh the page or restart my development server again

@prasanjit101 commented on GitHub (Mar 21, 2025): additional point: This bug persists even if I refresh the page or restart my development server again
Author
Owner

@prasanjit101 commented on GitHub (Mar 21, 2025):

Update: something was wrong with the secondary storage. It always picked up the stale data. Removed it and it is working now

@prasanjit101 commented on GitHub (Mar 21, 2025): Update: something was wrong with the secondary storage. It always picked up the stale data. Removed it and it is working now
Author
Owner

@kazmi066 commented on GitHub (Mar 23, 2025):

What if I want my old schema defined in mongoose to be used as better-auth "user".

import { Document, InferSchemaType, Schema, model, models } from 'mongoose';

export const UserRoles = {
  USER: 'user',
  ADMIN: 'admin',
  OWNER: 'owner',
} as const;
export type UserRole = (typeof UserRoles)[keyof typeof UserRoles];

const UserSchema = new Schema({
  email: {
    type: String,
    unique: [true, 'Email already exists!'],
    required: [true, 'Email is required!'],
  },
  name: {
    type: String,
    required: [true, 'Name is required!'],
  },
  password: { type: String, required: true },
  roles: { type: [String], enum: UserRoles, default: ['user'] },
  isVerified: { type: Boolean, default: false },
  image: { type: String, default: null },
  businesses: [{ type: Schema.Types.ObjectId, ref: 'Business' }],
});

export type User = InferSchemaType<typeof UserSchema> & Document;
export const UserModel = models.User || model('User', UserSchema);

I've tried modeling it to same collection name which works but ofcourse it is built upon its own collection schema so I don't get the image, businesses, roles etc.

import { betterAuth } from 'better-auth';
import { mongodbAdapter } from 'better-auth/adapters/mongodb';
import { MongoClient } from 'mongodb';

const client = new MongoClient(process.env.MONGODB_URI!);
const db = client.db();

export const auth = betterAuth({
  database: mongodbAdapter(db),
  user: {
    modelName: 'users',
  },
  emailAndPassword: {
    enabled: true,
  },
});
@kazmi066 commented on GitHub (Mar 23, 2025): What if I want my old schema defined in mongoose to be used as better-auth "user". ```ts import { Document, InferSchemaType, Schema, model, models } from 'mongoose'; export const UserRoles = { USER: 'user', ADMIN: 'admin', OWNER: 'owner', } as const; export type UserRole = (typeof UserRoles)[keyof typeof UserRoles]; const UserSchema = new Schema({ email: { type: String, unique: [true, 'Email already exists!'], required: [true, 'Email is required!'], }, name: { type: String, required: [true, 'Name is required!'], }, password: { type: String, required: true }, roles: { type: [String], enum: UserRoles, default: ['user'] }, isVerified: { type: Boolean, default: false }, image: { type: String, default: null }, businesses: [{ type: Schema.Types.ObjectId, ref: 'Business' }], }); export type User = InferSchemaType<typeof UserSchema> & Document; export const UserModel = models.User || model('User', UserSchema); ``` I've tried modeling it to same collection name which works but ofcourse it is built upon its own collection schema so I don't get the image, businesses, roles etc. ```ts import { betterAuth } from 'better-auth'; import { mongodbAdapter } from 'better-auth/adapters/mongodb'; import { MongoClient } from 'mongodb'; const client = new MongoClient(process.env.MONGODB_URI!); const db = client.db(); export const auth = betterAuth({ database: mongodbAdapter(db), user: { modelName: 'users', }, emailAndPassword: { enabled: true, }, }); ```
Author
Owner

@Samy0f commented on GitHub (Mar 25, 2025):

It's happening because you also need to update the cache in the secondary storage.
This is how I update my cache:


const session = await auth.api.getSession({
  headers: await headers(),
});

if (!session) {
  throw new Error("User not authenticated");
}

const token = session.session.token;

if (token) {
  const cachedUser = await getCache(token); // redis.get(token)
  if (cachedUser) {
    const cachedSession = JSON.parse(cachedUser) as Session;
    if (cachedSession.user.id === userId) {
      cachedSession.user.xp = updated.xp;
      cachedSession.user.rank = updated.rank;
      cachedSession.user.maxRank = updated.maxRank;
      cachedSession.user.streaks = updated.streaks;
      cachedSession.user.maxStreak = updated.maxStreak;
      cachedSession.user.lastActivityDate = updated.lastActivityDate;
      await setCache(token, JSON.stringify(cachedSession), {
        EX: 2 * 60 * 60,
      });
    }
  }
}


@Samy0f commented on GitHub (Mar 25, 2025): It's happening because you also need to update the cache in the secondary storage. This is how I update my cache: ```ts const session = await auth.api.getSession({ headers: await headers(), }); if (!session) { throw new Error("User not authenticated"); } const token = session.session.token; if (token) { const cachedUser = await getCache(token); // redis.get(token) if (cachedUser) { const cachedSession = JSON.parse(cachedUser) as Session; if (cachedSession.user.id === userId) { cachedSession.user.xp = updated.xp; cachedSession.user.rank = updated.rank; cachedSession.user.maxRank = updated.maxRank; cachedSession.user.streaks = updated.streaks; cachedSession.user.maxStreak = updated.maxStreak; cachedSession.user.lastActivityDate = updated.lastActivityDate; await setCache(token, JSON.stringify(cachedSession), { EX: 2 * 60 * 60, }); } } } ```
Author
Owner

@moshetanzer commented on GitHub (Mar 25, 2025):

Hey @kazmi066,

If you haven’t resolved your question, I think it’s best to open a new issue as it is not really connected to OP question ❤️

@moshetanzer commented on GitHub (Mar 25, 2025): Hey @kazmi066, If you haven’t resolved your question, I think it’s best to open a new issue as it is not really connected to OP question ❤️
Author
Owner

@Bekacru commented on GitHub (Mar 25, 2025):

you can also use the builtin updateUser to update both the secondary storage and the main db values

@Bekacru commented on GitHub (Mar 25, 2025): you can also use the builtin `updateUser` to update both the secondary storage and the main db values
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#886