Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Session Callback should return the updated session if possible #2560

Closed
matha-io opened this issue Aug 19, 2021 · 6 comments
Closed

Session Callback should return the updated session if possible #2560

matha-io opened this issue Aug 19, 2021 · 6 comments
Labels
enhancement New feature or request

Comments

@matha-io
Copy link

matha-io commented Aug 19, 2021

Description 📓

Hello everyone !

We use Next-auth in our company, with a custom adapter for FaunaDB. I already posted a discussion but no answers.

I tried to check again the issue and found that the returning session was always the old one & not the updated one, as you can see here.

  • We have an accessToken which is valid for the current session.
  • When the session needs to be updated, the token is recreated (the old one is removed automatically from Fauna because of its ttl).

So why not return the updated session instead ? It could help with token rotation.

Thank you !

How to reproduce ☕️

callbacks: {
  async session(session, user) {
    return session; // currently, it's always the non-updated session
    // the session.accessToken is therefore sometimes unvalid (expired ttl),
    // The session gets updated (with new active accessToken), but not available here :(
  },
}
// Updated code in /src/server/routes/session.js:L82
const session = await getSession(sessionToken)
    
if (session) {
    // await updateSession(session) OLD
    const updatedSession = await updateSession(session) // NEW

    const user = await getUser(session.userId)

    // By default, only exposes a limited subset of information to the client
    // as needed for presentation purposes (e.g. "you are logged in as…").
    const defaultSessionPayload = {
        user: {
            name: user.name,
            email: user.email,
            image: user.image,
        },
        // accessToken: session.accessToken, OLD
        // expires: session.expires, OLD
        accessToken: updatedSession.accessToken, // NEW: updated accessToken (for rotation)
        expires: updatedSession.expires, // NEW: updated expires
    }

    // Pass Session through to the session callback
    const sessionPayload = await callbacks.session(
        defaultSessionPayload,
        user
    )

    // Return session payload as response
    response = sessionPayload

    // Set cookie again to update expiry
    cookie.set(res, cookies.sessionToken.name, sessionToken, {
        expires: session.expires,
        ...cookies.sessionToken.options,
    })

    await dispatchEvent(events.session, { session: sessionPayload })
}

Contributing 🙌🏽

Yes, I am willing to help implement this feature in a PR

@matha-io matha-io added the enhancement New feature or request label Aug 19, 2021
@matha-io matha-io changed the title Adapter Session Callback should return the updated session if possible Session Callback should return the updated session if possible Aug 19, 2021
@balazsorban44
Copy link
Member

balazsorban44 commented Aug 19, 2021

You just got the session with getSession, your suggested changes won't do any good.

In v3 (current stable):

  • getSession is supposed to get the session from the DB, and delete it if it was expired
  • updateSession is only supposed to update the expiry date

This is very hard to understand from the above code (so I can understand your confusion), but you can see the current built-in Prisma adapter's implementation for reference:

https://github.com/nextauthjs/adapters/blob/main/packages/prisma-legacy/src/index.js#L136-L192

In the upcoming version, these responsibilities are moved to the core, see the Prisma adapter with updated getSessionAndUser (prev getSession), and updateSession methods:

https://github.com/nextauthjs/adapters/blob/5c915823c43867dd54adf3977c26968baa5ec11f/packages/prisma/src/index.ts#L21-L32

And the updated session handler code:

let userAndSession = await getSessionAndUser(sessionToken)
// If session has expired, clean up the database
if (
userAndSession &&
userAndSession.session.expires.valueOf() < Date.now()
) {
await deleteSession(sessionToken)
userAndSession = null
}
if (userAndSession) {
const { user, session } = userAndSession
const sessionUpdateAge = req.options.session.updateAge
// Calculate last updated date to throttle write updates to database
// Formula: ({expiry date} - sessionMaxAge) + sessionUpdateAge
// e.g. ({expiry date} - 30 days) + 1 hour
const sessionIsDueToBeUpdatedDate =
session.expires.valueOf() -
sessionMaxAge * 1000 +
sessionUpdateAge * 1000
const newExpires = fromDate(sessionMaxAge)
// Trigger update of session expiry date and write to database, only
// if the session was last updated more than {sessionUpdateAge} ago
if (sessionIsDueToBeUpdatedDate <= Date.now()) {
await updateSession({ sessionToken, expires: newExpires })
}

There is already an issue for discussing session updates #2269

Built-in token rotation is planned in the future.

@matha-io
Copy link
Author

Hey @balazsorban44, I see from the upcoming version that there is no accessToken anymore.
Is there any preview of the built-in token rotation ? Can I help in any way ? When would it be released ?
Because atm I will have to make a hotfix to make this work for me :)

@balazsorban44
Copy link
Member

balazsorban44 commented Aug 19, 2021

Access tokens come from your provider, and usually saved on accounts. No ETA for token rotation

@matha-io
Copy link
Author

matha-io commented Aug 19, 2021

My token is not coming from the Provider (also I have multiple provider).
I am using Database sessions with Next-Auth + Fauna.

Fauna can give me ABAC tokens which I want to sync with the session (which accepts accessToken property atm).
I guess I don't really know how to refresh them here in the server-side. I was updating the accessToken alongside the session.

const FQL = q.Update(
    q.Ref(q.Collection(collections.Session), session.id),
    {
        ttl: q.TimeAdd(q.Now(), sessionMaxAge, 'milliseconds'),
        data: {
        // added accessToken prop
          accessToken: q.Select(
              'secret',
              q.Create(q.Tokens(), {
                instance: q.Ref(q.Collection('users'), session.userId),
                ttl: q.TimeAdd(q.Now(), accessTokenTtl, 'milliseconds'),
              }),
          ),
          expires: q.TimeAdd(q.Now(), sessionMaxAge, 'milliseconds'),
          updatedAt: q.Now(),
        },
    },
);

@balazsorban44
Copy link
Member

balazsorban44 commented Aug 19, 2021

Tokens are usually unique per provider, so it only makes sense to store them on accounts.

I believe OAuth providers are already required to return an access_token, but not all of them support token rotation (meaning you don't even receive a refresh_token from them).

The next version will make it possible to override certain adapter methods though, so you will still be able to create your own adapter, based on the official ones. The official Fauna Adapter is already rewritten for the new API, see it here for inspiration:

https://github.com/nextauthjs/adapters/blob/5c915823c43867dd54adf3977c26968baa5ec11f/packages/fauna/src/index.ts

@matha-io
Copy link
Author

matha-io commented Aug 20, 2021

Wow magnificent !
Thanks, eager to see it live ! Any ETA for the V4 ? Is there a way to follow it ?

EDIT : Oops, it's here : https://github.com/nextauthjs/next-auth/tree/next

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants