setUserSession in sessionHooks did not update client session data
Users might log into the website using multiple clients, such as mobile phones and computers. When a user updates their information on the mobile end, I want to check upon refreshing the page on the PC end if there have been updates and then reset the session data.
Therefore, in sessionHooks, I query the user table and compare timestamps. If the session login time is earlier than the data update time, I use setUserSession to update the session data. It successfully updates on the server side, but the user data obtained with useUserSession on the client side does not reflect the updates.
I found a solution to this issue (https://github.com/atinux/nuxt-auth-utils/issues/357) that suggests using session.user = getSessionData(user) and it works correctly.
However, I’m concerned that this might be a bug rather than a feature and might not be supported in future versions. Is my concern unfounded?
export default defineNitroPlugin(() => {
sessionHooks.hook('fetch', async (session, ev) => {
const db = useDB(ev)
const { users } = tables
if (session.user) {
const [user] = await db
.select()
.from(users)
.where(eq(users.id, session.user.id))
if (user) {
if (user.status !== 'ACTIVE') {
await clearUserSession(ev)
throw createError({
statusCode: 401,
message: 'Invalid token'
})
}
console.log(session.user.lastLoginAt, user.updatedAt)
if (session.user.lastLoginAt < user.updatedAt) { // update user data on other client
// didn't work
// await setUserSession(ev, {
// user: getSessionData(user)
// })
// it's work
session.user = getSessionData(user)
}
}
}
})
})
Have you try this one
await replaceUserSession(event, data)
I’ve been looking into this and I don’t think the current behavior is intentional - or at least it’s not well documented.
The behavior seems to come from the server API that returns the session: https://github.com/atinux/nuxt-auth-utils/blob/71737c2b6ae373bc418f9d7f3e3ada8f55ef193e/src/runtime/server/api/session.get.ts#L5-L14
That API is used by the composable here: https://github.com/atinux/nuxt-auth-utils/blob/71737c2b6ae373bc418f9d7f3e3ada8f55ef193e/src/runtime/app/composables/session.ts#L30
The composable is used in the plugins: https://github.com/atinux/nuxt-auth-utils/blob/71737c2b6ae373bc418f9d7f3e3ada8f55ef193e/src/runtime/app/plugins/session.server.ts#L14
From my perspective, the implementation would make more sense like this (probably it doesn't work, just to show what I mean):
export default eventHandler(async (event) => {
const session = await getUserSession(event)
// If the session is not empty, call the fetch hook
if (Object.keys(session).length > 0) {
// Directly mutating the session object here feels discouraged
await sessionHooks.callHookParallel('fetch', session, event)
}
// Retrieve the (potentially) updated session
const maybeUpdatedSession = await getUserSession(event)
const { secure, ...data } = maybeUpdatedSession
return data
})
This way, any hook mutations don’t rely on mutating the session object by reference and it's more consistent. Plus it avoids having the value returned different from the one returned as a cookie.
That said, I realize some users may already depend on the current behavior, so changing this directly might be a breaking change.
Maybe the best approach would be to support both:
- mutation by reference (for backward compatibility)
- an explicit way to update the session safely (preferred going forward)
What do you think, @atinux?
I think there is another point too, similar to the one discussed for useCookie (es. https://github.com/nuxt/nuxt/pull/21940)
During ssr any api call to a server api that mutates the session does not mutate the session sent from the browser.
For example:
- user visit
/adminsending a session like{ user: { id: 123 } } - then the server plugin call
/api/_auth/sessionto retrieve the session - before it's returned, it's updated by a fetch hook
{ user: { id: 123, role: "Admin" } } - the page starts rendering and
/api/admin/datais called /api/admin/datatry to read the session but the session is{ user: { id: 123 } }and not{ user: { id: 123, role: "Admin" } }
I'm not sure about this, I think there are 2 solutions:
- mutating the original request cookies (like the Nuxt PR above)
- mutating something else, maybe
event.context.sessions?.[sessionName](I didn't focus on this solution so I'm not sure it makes any sense)