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

getSessionAndUser called 2x per page load when adapter is specified #4605

Closed
timfee opened this issue May 22, 2022 · 14 comments
Closed

getSessionAndUser called 2x per page load when adapter is specified #4605

timfee opened this issue May 22, 2022 · 14 comments
Assignees

Comments

@timfee
Copy link

timfee commented May 22, 2022

Environment

  System:
    OS: macOS 12.3.1
    CPU: (10) arm64 Apple M1 Max
    Memory: 26.36 GB / 64.00 GB
    Shell: 5.8 - /bin/zsh
  Binaries:
    Node: 16.15.0 - /usr/local/bin/node
    Yarn: 1.22.19 - /opt/homebrew/bin/yarn
    npm: 8.5.5 - /usr/local/bin/npm
  Browsers:
    Chrome: 101.0.4951.64
    Safari: 15.4
  npmPackages:
    next: 12.1.6 => 12.1.6
    next-auth: ^4.3.4 => 4.3.4
    react: 18.1.0 => 18.1.0

Reproduction URL

https://github.com/timfee/nextauth-dupe-calls

Describe the issue

I enabled verbose logging in the config file:

After attaching an adapter (in this case, Prisma), I notice two adapter_getSessionAndUser log events per page load. I only have a <SessionProvider> context in _app.tsx and a useSession in index.tsx.

diff

Even if you change the index.tsx to remove the call to useSession, it still generates double the query.

NEXTAUTH_URL
adapter_getSessionAndUser { args: [ '9015a8b2-4ffe-4109-b4a9-60c15da149b9' ] }
prisma:query SELECT "public"."Session"."id", "public"."Session"."sessionToken", "public"."Session"."userId", "public"."Session"."expires" FROM "public"."Session" WHERE "public"."Session"."sessionToken" = $1 LIMIT $2 OFFSET $3
prisma:query SELECT "public"."User"."id", "public"."User"."name", "public"."User"."email", "public"."User"."emailVerified", "public"."User"."image" FROM "public"."User" WHERE "public"."User"."id" IN ($1) OFFSET $2
NEXTAUTH_URL
adapter_getSessionAndUser { args: [ '9015a8b2-4ffe-4109-b4a9-60c15da149b9' ] }
prisma:query SELECT "public"."Session"."id", "public"."Session"."sessionToken", "public"."Session"."userId", "public"."Session"."expires" FROM "public"."Session" WHERE "public"."Session"."sessionToken" = $1 LIMIT $2 OFFSET $3
prisma:query SELECT "public"."User"."id", "public"."User"."name", "public"."User"."email", "public"."User"."emailVerified", "public"."User"."image" FROM "public"."User" WHERE "public"."User"."id" IN ($1) OFFSET $2

Is this WAI, or are there any ways to de-dupe the request? I followed the SessionProvider pattern, which the docs say should avoid checking the session twice on pages that support both server and client side rendering.

Thanks!

How to reproduce

Clone the repro URL and populate the .env files to a Postgres instance.

Expected behavior

I would expect (perhaps mistakenly :)) that there wouldn't be double SELECTs per page load.

@timfee timfee added the triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime. label May 22, 2022
@timfee timfee changed the title getSessionAndUser called 2x per page load when adapter is specified getSessionAndUser called 2x per page load when adapter is specified May 22, 2022
@timfee
Copy link
Author

timfee commented May 23, 2022

I sort of skipped over the obvious part; it looks like the /api/session endpoint is called twice!

Screen Shot 2022-05-22 at 17 20 51

@ThangHuuVu
Copy link
Member

I can't access the reproduction URL, could you share it?
Nevertheless, the pattern requires you to provide the pageProps correctly, as described in the docs:

This only works on pages where you provide the correct pageProps, however. This is normally done in getInitialProps or getServerSideProps on an individual page basis like so:
import { getSession } from "next-auth/react"

export async function getServerSideProps(ctx) {
  return {
    props: {
      session: await getSession(ctx)
    }
  }
}

Hope it helps!

@ThangHuuVu ThangHuuVu removed the triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime. label May 23, 2022
@timfee
Copy link
Author

timfee commented May 23, 2022

Updated the visibility of the repo.

Also, I think I inadvertently made the issue worse -- in my attempts to strip out any getSession calls, I removed the session prop.

adding that piece does eliminate the second server call, however there are still two calls to the adapter on initial load, so just wanted to make sure that was expected behavior.

thanks for the help!

adapter_getSessionAndUser { args: [ '7a147a17-4bd6-4c43-91ff-a3a3424676ec' ] }
prisma:query SELECT "public"."Session"."id", "public"."Session"."sessionToken", "public"."Session"."userId", "public"."Session"."expires" FROM "public"."Session" WHERE "public"."Session"."sessionToken" = $1 LIMIT $2 OFFSET $3
prisma:query SELECT "public"."User"."id", "public"."User"."name", "public"."User"."email", "public"."User"."emailVerified", "public"."User"."image" FROM "public"."User" WHERE "public"."User"."id" IN ($1) OFFSET $2

adapter_getSessionAndUser { args: [ '7a147a17-4bd6-4c43-91ff-a3a3424676ec' ] }
prisma:query SELECT "public"."Session"."id", "public"."Session"."sessionToken", "public"."Session"."userId", "public"."Session"."expires" FROM "public"."Session" WHERE "public"."Session"."sessionToken" = $1 LIMIT $2 OFFSET $3
prisma:query SELECT "public"."User"."id", "public"."User"."name", "public"."User"."email", "public"."User"."emailVerified", "public"."User"."image" FROM "public"."User" WHERE "public"."User"."id" IN ($1) OFFSET $2

@ThangHuuVu ThangHuuVu self-assigned this May 24, 2022
@ThangHuuVu
Copy link
Member

hmm, if there are no callbacks during the initial load, I expect only one adapter call from one API call. Here is the relevant code:

let userAndSession = await getSessionAndUser(sessionToken)

I tried but couldn't reproduce this behavior using your example or our internal development app. 🤔 Can you confirm that this is still happening?

@timfee
Copy link
Author

timfee commented May 24, 2022

Oh, weird!

Yeah, still happening. In fact, even in a signed-out state, I'm seeing two NEXTAUTH_URL warnings per page load:

[next-auth][warn][NEXTAUTH_URL]
https://next-auth.js.org/warnings#nextauth_url
[next-auth][warn][NEXTAUTH_URL]
https://next-auth.js.org/warnings#nextauth_url

and when logged in, I see the duplicate calls:

[next-auth][warn][NEXTAUTH_URL]
https://next-auth.js.org/warnings#nextauth_url
[next-auth][debug][adapter_getSessionAndUser] { args: [ '5e72abb2-1669-4ced-b862-d01abb1a4657' ] }
prisma:query SELECT "public"."Session"."id", "public"."Session"."sessionToken", "public"."Session"."userId", "public"."Session"."expires" FROM "public"."Session" WHERE "public"."Session"."sessionToken" = $1 LIMIT $2 OFFSET $3
prisma:query SELECT "public"."User"."id", "public"."User"."name", "public"."User"."email", "public"."User"."emailVerified", "public"."User"."image" FROM "public"."User" WHERE "public"."User"."id" IN ($1) OFFSET $2
[next-auth][warn][NEXTAUTH_URL]
https://next-auth.js.org/warnings#nextauth_url
[next-auth][debug][adapter_getSessionAndUser] { args: [ '5e72abb2-1669-4ced-b862-d01abb1a4657' ] }
prisma:query SELECT "public"."Session"."id", "public"."Session"."sessionToken", "public"."Session"."userId", "public"."Session"."expires" FROM "public"."Session" WHERE "public"."Session"."sessionToken" = $1 LIMIT $2 OFFSET $3
prisma:query SELECT "public"."User"."id", "public"."User"."name", "public"."User"."email", "public"."User"."emailVerified", "public"."User"."image" FROM "public"."User" WHERE "public"."User"."id" IN ($1) OFFSET $2

FWIW, I'm using yarn dev (not sure if development environments might be less optimized?)

I just updated the repo slightly to clean up a few extra bits to make it as straightforward of a repo as possible.

@ThangHuuVu
Copy link
Member

ThangHuuVu commented May 26, 2022

using the repo you provided, If I replace getSession with getServerSession, I only see one warning per load. getServerSession is also recommended, it does not call the API via the NextAuth handler again like calling getSession from getServerSideProps, which is an anti-pattern, so I suggest you use that. See: #4116
About the query, I can't see the logs after my first run, even when I ran prisma.$disconnect() 🤔 any ideas on how I can get the logs to show again?

@timfee
Copy link
Author

timfee commented May 26, 2022

using the repo you provided, If I replace getSession with getServerSession, I only see one warning per load.

I tried to do that (see updated repro without any calls to getServerSession); just the Provider.

getServerSession is also recommended, it does not call the API via the NextAuth handler again like calling getSession from getServerSideProps, which is an anti-pattern, so I suggest you use that. See: #4116

That's great to know! Will do that going forward. I noticed that when I made that change, I was getting NEXTAUTH_SECRET warnings (even though it's specified in .env; though it's not explicitly passed through to a secret param via process.env).

About the query, I can't see the logs after my first run, even when I ran prisma.$disconnect() 🤔 any ideas on how I can get the logs to show again?

Let me try to dig in more here!

@timfee
Copy link
Author

timfee commented May 26, 2022

Sorry this is so thorny! Here's what I see when I run:

Screen Shot 2022-05-26 at 08 28 24

@ThangHuuVu
Copy link
Member

Thanks, @timfee, it's super weird because I can reproduce this behavior on your repo but not other repos... 🤔 I honestly don't know what caused this. It makes sense if getServerSession is called, but in this case, there are two calls even if getServerSession is removed. @balazsorban44 any ideas on this? 😄

@balazsorban44
Copy link
Member

I believe this might be expected. Once in callback-handler:

const userAndSession = await getSessionAndUser(sessionToken)

And again when we fetch the initial session client-side:

__NEXTAUTH._getSession()

@timfee
Copy link
Author

timfee commented May 31, 2022

Thanks for the clarification!

In my POC, I was trying to be extremely judicious about reads / writes to my database, and it seemed a bit off that there were two (duplicated) SELECT calls made by providing the Session in _app.tsx

https://github.com/timfee/nextauth-dupe-calls/blob/3f4778babab7fffea9db3e7325cdc740864c1ee3/pages/_app.tsx#L14-L16

@balazsorban44
Copy link
Member

If you pass a session as a prop (eg from getServerSideProps), the initial fetch is skipped on the client:

const hasInitialSession = props.session !== undefined
/** If session was passed, initialize as already synced */
__NEXTAUTH._lastSync = hasInitialSession ? now() : 0
const [session, setSession] = React.useState(() => {
if (hasInitialSession) __NEXTAUTH._session = props.session
return props.session
})
/** If session was passed, initialize as not loading */
const [loading, setLoading] = React.useState(!hasInitialSession)
React.useEffect(() => {
__NEXTAUTH._getSession = async ({ event } = {}) => {
try {
const storageEvent = event === "storage"
// We should always update if we don't have a client session yet
// or if there are events from other tabs/windows
if (storageEvent || __NEXTAUTH._session === undefined) {

@timfee
Copy link
Author

timfee commented May 31, 2022

@balazsorban44: this doesn't work, see my repro that still generates 2x calls:

[next-auth][warn][NO_SECRET]
https://next-auth.js.org/warnings#no_secret
[next-auth][debug][adapter_getSessionAndUser] { args: [ 'fc903865-d5be-460b-abf1-858fb6e9e28a' ] }
prisma:query SELECT "public"."Session"."id", "public"."Session"."sessionToken", "public"."Session"."userId", "public"."Session"."expires" FROM "public"."Session" WHERE "public"."Session"."sessionToken" = $1 LIMIT $2 OFFSET $3
prisma:query SELECT "public"."User"."id", "public"."User"."name", "public"."User"."email", "public"."User"."emailVerified", "public"."User"."image" FROM "public"."User" WHERE "public"."User"."id" IN ($1) OFFSET $2
wait  - compiling /api/auth/[...nextauth]...
wait  - compiling...
event - compiled client and server successfully in 29 ms (178 modules)
[next-auth][warn][NEXTAUTH_URL]
https://next-auth.js.org/warnings#nextauth_url
[next-auth][debug][adapter_getSessionAndUser] { args: [ 'fc903865-d5be-460b-abf1-858fb6e9e28a' ] }
prisma:query SELECT "public"."Session"."id", "public"."Session"."sessionToken", "public"."Session"."userId", "public"."Session"."expires" FROM "public"."Session" WHERE "public"."Session"."sessionToken" = $1 LIMIT $2 OFFSET $3
prisma:query SELECT "public"."User"."id", "public"."User"."name", "public"."User"."email", "public"."User"."emailVerified", "public"."User"."image" FROM "public"."User" WHERE "public"."User"."id" IN ($1) OFFSET $2

@ThangHuuVu
Copy link
Member

@balazsorban44 actually, the callback-handler is never called with a refresh; it is only called in the sign-in flow.
When I checked in our dev app:

  • Without getServerSession: The session route is hit once
  • With getServerSession: The session route is hit twice, which is expected because one is called in the default API handler, one again in the getServerSession method.
    What I don't understand in the repro is that somehow it still shows 2x calls logs even without the getServerSession call. 😵

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

No branches or pull requests

3 participants