atinux / nuxt-auth-utils

Add Authentication to Nuxt applications with secured & sealed cookies sessions.
MIT License
974 stars 91 forks source link

Verify and enrich session on server-side #203

Open tobiasdiez opened 1 month ago

tobiasdiez commented 1 month ago

Often one needs to check if a session is still valid on the server-side. For example, one may want to present the user with an option to logout on all devices, which then should invalidate all existing sessions.

For this, one needs to query for every request the a sever-side session storage (and perhaps update it). Currently, this is relatively hard to implement. A few suggestions to make this easier:

  1. Implement a useRawSession method to directly access the h3 session (in other words make https://github.com/atinux/nuxt-auth-utils/blob/e0396255b2ebc6b3ef89b9dd62f0eb0fbf345389/src/runtime/server/utils/session.ts#L96 public) - alternatively, add the sessionId to the public interface of UserSession
  2. Implement a server middleware that calls a hook where devs can check the validity of the sessionId (and update timestamps like "last active" etc)
  3. ...and perhaps return some data that one would like to associate with the current session on all server routes but don't directly store in the user cookie (e.g. permissions)

Point two and three might look like:

export default defineEventHandler(async (event) => {
  const session = await _useSession(event)
  // session.id is not a good way to check if there is a session as it will always be set by h3 (except if one calls clearSession)
  // use session.data instead, with the convention that if it is an empty object, there is no session ?
  if (session.id && Object.keys(session.data).length !== 0) {
    try {
      // Check if session is valid
      const info = await hooks.call('validateSession', {id: session.id, data: session.data})
      // Is there a better to store data in the session object?
      session.server = info
    } catch {
      // Clear session
      await session.clear()
    }
  }
})
atinux commented 1 month ago

Did you take a look at the fetch hook (https://github.com/atinux/nuxt-auth-utils?tab=readme-ov-file#extend-session) to already implement this logic?

tobiasdiez commented 1 month ago

Yes, I did. But this is not run on server-routes (right?) and doesn't provide the sessionId.

atinux commented 1 month ago

What about creating a server util to wrap requireUserSession(event)?

// server/utils/session.ts
export async function requireValidUserSession(event) {
  const session = await requireUserSession(event)

  // Do your logic here or throw createError(...)
  // return the extended session
  return session
})

What we can do is to expose the session.id, or you can do you own with:

await setUserSession({ id: randomUUID(), user: { ... } })
tobiasdiez commented 1 month ago

Yes, the approach via requireValidUserSession would work as well and I'm using something like this in https://github.com/JabRef/JabRefOnline/blob/main/server/middleware/validateSession.ts. I just thought that it is a very common pattern and thus would have liked the nuxt-auth module to expose such a hook.

atinux commented 1 month ago

it's tricky as you may want to add some local cache to avoid extending everytime based on the ID, at least you have a workaround :)