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

Is it possible to prevent api/auth/session from being called multiple times? #6630

Closed
frontlearner28 opened this issue Feb 6, 2023 · 30 comments
Labels
question Ask how to do something or how something works

Comments

@frontlearner28
Copy link

Question 💬

Currently, I use getSession() in my Axios interceptor, to control valid session. The problem is that the session is being called multiple times on pages with many API calls, increasing my page load time too much.

Is there any way to prevent this?

CleanShot 2023-02-06 at 11 46 25

How to reproduce ☕️

To reproduce the problem, use getSession in an Axios interceptor and execute many calls on one page

Contributing 🙌🏽

Yes, I am willing to help answer this question in a PR

@frontlearner28 frontlearner28 added the question Ask how to do something or how something works label Feb 6, 2023
@balazsorban44
Copy link
Member

You should not use useSession for axios, as it's a React api. check out the getSession method instead.

@frontlearner28
Copy link
Author

@balazsorban44 but in my question I explained that I use getSession, at no point did I comment about useSession. Can you please reopen the issue? :c

@balazsorban44
Copy link
Member

balazsorban44 commented Feb 6, 2023

I need my glasses... 👀 This is likely still not a NextAuth.js bug for what it's worth, but how you use axios, I assume.

getSession is essentially just a json request to the /api/auth/session endpoint, it does no polling or the like.

@Ali-hd
Copy link

Ali-hd commented Dec 5, 2023

Hi @frontlearner28 I am facing the same issue as you, did you find out have you can reduce the api requests every time you use getSession() ?

@Ali-hd
Copy link

Ali-hd commented Dec 5, 2023

I need my glasses... 👀 This is likely still not a NextAuth.js bug for what it's worth, but how you use axios, I assume.

getSession is essentially just a json request to the /api/auth/session endpoint, it does no polling or the like.

Hi @balazsorban44 I understand that getSession is only used to get session info but is there a better way to use it? In client side I need to send the access token with many requests. With every request I need to call the method getSession() to get the Auth token, I don't have any other way in mind. Can I save the session data locally in the browser or something?

@patelmilanun
Copy link

@Ali-hd did u found any solution to this. @balazsorban44 can u suggest some better approach or link to something that can help here?

@balazsorban44
Copy link
Member

You can for example proxy the API requests through the Next.js backend and attach the session there. You can get the session server-side without extra network trips.

@patelmilanun
Copy link

Well actually, api/auth/session is basically the same. So from client side we are calling proxy api then it will be calling the api just like /api/auth/session as both of the things will be at next.js backend.

So either call api/auth/session and get token and then call the actual api or call the proxy api. I think in 2nd approch we can save 1 network trip from client side.

@patelmilanun
Copy link

TL;DR;
Use https://github.com/nextauthjs/next-auth-example/blob/main/app/client-example/page.tsx and set the "zustand" state in client component so in https://github.com/nextauthjs/next-auth-example/blob/main/components/client-example.tsx

So actually I was implementing it for 2 things. First is openapi-fetch client to attach auth on every request similar to axios and is already implemented using the getSession() which was causing the problem and cant make it hook as it might break and will have to do refactor since it is not react component. Second is to check permission access for specific function of application where I need to compare permission required to access that resource vs the session permissions.

So I ended up using state management library "zustand" note that u should not use it to share state across server component and client component. So using their official document for how to implement auth in client component (basically call auth() and get session, then take that session and pass it along with session provider which is wrapping the client component, and use useSession()). I used this trick on main layout file and then just in client component I set the state of zustand. This allowed me to access latest value of zustand state everywhere even in non-component file.

So u can implement this approach. Thanks for help guys!

@nghianguyen119
Copy link

I saw this issue after migrating to V5, but if it's the issue of next-auth then I can rest assured, the team need time to refactor

@mauricioleite1
Copy link

I'm using V5 and this keeps happening.. session and jwt callbacks happening 6 times one after another :/ I tried everything

@john-subba
Copy link

I'm using V5 and this keeps happening.. session and jwt callbacks happening 6 times one after another :/ I tried everything

Yeah. I am rethinking whether to use this or not. The session callback api get's called so many times. Even if i have not used useSession() hook

@HiroakiLion
Copy link

Having the same issue, for now I am caching the data I received to prevent multiple api calls based on useSession, but everytime i use useSession, the session calls are increasing rapidly.

@john-subba
Copy link

Having the same issue, for now I am caching the data I received to prevent multiple api calls based on useSession, but everytime i use useSession, the session calls are increasing rapidly.

i think if u use the session provider in specific components that u need u can tackle this issue even without caching it.
i previously wrapped everything with the provider in the layout.tsx maybe that was the reason it get's called multiple times.

@anshuopinion
Copy link

import { getBackendServerBaseUrlByEnvironment } from "@/utils/environmentUtil";
import axios from "axios";
import { getSession } from "next-auth/react";

// Singleton instance of Axios Auth Client
let axiosAuthClientInstance: ReturnType<typeof axiosAuthClient> | null = null;

/**
 * Get Axios Auth Client Instance
 * @dev - critical wrapper for handling api authorization to the backend
 *
 * Returns the singleton instance of the Axios Auth Client. If the instance
 * does not exist, it creates one.
 *
 * @returns {AxiosInstance} The singleton Axios instance.
 */
export const getAxiosAuthClientInstance = () => {
  if (!axiosAuthClientInstance) {
    axiosAuthClientInstance = axiosAuthClient();
  }
  return axiosAuthClientInstance;
};

/**
 * Axios Auth Client
 *
 * Creates an Axios instance configured to include the authorization token
 * from the NextAuth session in the request headers. This instance is created
 * once and reused throughout the application.
 *
 * @returns {AxiosInstance} The configured Axios instance.
 */
const axiosAuthClient = () => {
  const instance = axios.create({
    baseURL: getBackendServerBaseUrlByEnvironment(),
    headers: {
      "Content-Type": "application/json",
    },
  });

  instance.interceptors.request.use(async (config) => {
    const session = await getSession();
    if (session && !config.headers["Authorization"]) {
      config.headers["Authorization"] = `Bearer ${session.accessToken}`;
    }
    return config;
  });

  instance.interceptors.response.use(
    (response) => response,
    (error) => {
      // Handle error response
      console.error("Request failed", error);
      return Promise.reject(error);
    }
  );

  return instance;
};

export default getAxiosAuthClientInstance;

Try this solution

@lunarW1TCH
Copy link

If anyone stumbles upon it I solved it by fetching the session in main layout of my app and passing it to a component which sets it in a zustand store. I am not calling useSession anymore as it seems to be the culprit.

@john-subba
Copy link

If anyone stumbles upon it I solved it by fetching the session in main layout of my app and passing it to a component which sets it in a zustand store. I am not calling useSession anymore as it seems to be the culprit.

useSession with the sessionProvider is the culprit if u use getServerSession it doesnt give the issue
but the problem if to use update() function to update the session u need to use useSession hook no other way around it.

@Renkas
Copy link

Renkas commented Jun 25, 2024

Why is this closed? It has not been solved ...

@iamvinny
Copy link

iamvinny commented Jul 18, 2024

I have the following code to obtain the session in a server component:

import { auth } from "@/auth"
import Form from "./_components/Form";

export default async function LoginPage() {
    const session = await auth()
    
    if(session) { 
        return (
            <div>{JSON.stringify(session)}</div>
        )
    }

    return (
        <Form />
    )

}

But for some reason, it is executing 7 times
Screenshot 2024-07-18 at 5 50 19 PM

These are my callbacks:

    callbacks: {
        async jwt({ token, account, user }: any) {
            if (user) {
                token.user = user
                token.accessToken = user.access_token
            }
            return token
        },
        async session({ session, token }: any) {
            console.log('session exists') // will run 7 times,
            session.accessToken = token.access_token
            session.user = token.user
            return session
        },
    },

@waseemthefword
Copy link

Does any found a optimal way to do it ? without repeating session like multiple times.

@NoahGdev

This comment has been minimized.

@lakinmindfire
Copy link

Do we have a solution yet ?

@MagisterUnivers
Copy link

Jesus. That /session is on rampage now (14 times call on just watching the page, and switch tabs)

@MrMissx
Copy link

MrMissx commented Oct 7, 2024

Got this issue on beta.21 and beta.22. Resolve it when downgrading to 5.0.0-beta.20

@michaeltroya
Copy link

looks like they decreased in 5.0.0-beta.20, still getting 4 calls but better than the 11 I was getting on beta.18

@lunarW1TCH
Copy link

lunarW1TCH commented Oct 11, 2024

As I see this is still a problem for some people.

I am checking session once on in my RootLayout.
I pass this session to a custom <SessionStoreProvider/> component.

const RootLayout = async (props: LayoutProps): Promise<JSX.Element> => {
  //...
  
  const session = (await auth()) as DbSession | null;

  return (
    <html>
      {/* ... */}
        <SessionStoreProvider session={session} />
      {/* ... */}
    </html>
  );
}

In that component I am using this session to set it into a global zustand store. I am also running fetch (with useSWR library by Vercel) every 5 minutes to update the session regularly if I don't do it in any of the server components.

'use client';

import { memo, useEffect } from 'react';
import useSWR, { mutate } from 'swr';

import { setSession } from '@/store/auth/actions/session';
import { fetcher } from '@/utils/api';

import type { DbSession } from '../../auth';

const SessionStoreProvider = (
  props: SessionStoreProviderProps,
): React.JSX.Element => {
  useEffect(() => {
    setSession(props.session);
  }, [props.session]);

  const { data, isLoading } = useSWR<DbSession | null>(
    '/api/auth/session',
    fetcher,
    {
      refreshInterval: 300000,
    },
  );

  useEffect(() => {
    if (isLoading) return;
    setSession(data ?? null);
  }, [data, isLoading]);

  return <></>;
};

export default memo(SessionStoreProvider);

export const mutateSession = (): Promise<unknown> =>
  mutate('/api/auth/session');

type SessionStoreProviderProps = {
  session: DbSession | null;
};

You can also update session at any point by calling mutate('/api/auth/session') anywhere in your client-side code.

Fetching the session client-side is done through zustand api instead of next-auth api.

With this I get complete control over the amount of calls to /api/auth/session endpoint.

Hope this helps.

@balazsorban44
Copy link
Member

For anyone reading this, this is not a bug.

Depending on your code, it could happen for multiple reasons.

If anyone would like me to dissect their exact usecase a bit more and see if we can document gotchas better or fix any potential bugs, please open a new bug report with a reproduction. This issue has been closed, and we generally don't monitor these. The OP's question about axios should have been answered, I'm seeing diverse questions in follow-up comments now.

Here is some of the general advice:

  1. for await auth() calls, if you see too many of them, they are not cached per request by default, so naturally any logic to your session will execute as many times as auth() was called during render

Solution: https://react.dev/reference/react/cache

We are considering to add this by default

  1. for useSession/ calls to /api/auth/session
  • Make sure polling isn't enabled/configured correctly
  • Multiple instances of SessionProvider's might cause extra calls, usually you don't need more than one of these

With the App Router, we would however recommend relying on auth() and pass down session data to your client components on a need basis.

I hope this helps! 🙏

@nimbit-software
Copy link

@balazsorban44 could you expound on the react/cache solution please?

@Yegoroot
Copy link

Yegoroot commented Nov 11, 2024

// contexts/CashedSessionContext.tsx
import { createContext, useContext, useState, useEffect, ReactNode } from 'react'
import { useSession } from 'next-auth/react'
import { Session } from 'next-auth'
import { setupAxiosInterceptors } from '@/api/utils/axios'

const CachedSessionContext = createContext<Session | null>(null)

interface SessionProviderProps {
  children: ReactNode
}

export function CachedSessionProvider({ children }: SessionProviderProps) {
  const { data: session } = useSession()
  const [cachedSession, setCachedSession] = useState<Session | null>(session)

  useEffect(() => {
    if (session) {
      setCachedSession(session)
      setupAxiosInterceptors(session) // HEEEERE
    }
  }, [session])

  return (
    <CachedSessionContext.Provider value={cachedSession}>
      {children}
    </CachedSessionContext.Provider>
  )
}


export function useCachedSession() {
  return useContext(CachedSessionContext)
}

axios instance

// utils/axios.ts
export const privateAxiosInstance = axios.create({
  baseURL: API_URL,
  withCredentials: true,
  headers: {
    'Content-Type': 'application/json',
  },
})


export function setupAxiosInterceptors(session: Session) {
  privateAxiosInstance.interceptors.request.use(
    (config) => {
      if (session?.accessToken) {
        config.headers.Authorization = `Bearer ${session.accessToken}`
      }
      return config
    },
    (error) => {
      const formattedError = {
        status: error.response?.status,
        message: error.response?.data?.message || error.message,
      }
      return Promise.reject(formattedError) 
    }
  )
}

now you can use useCachedSession and axios is configured

@amaryassa
Copy link

amaryassa commented Nov 18, 2024

On "next-auth": "^5.0.0-beta.25" , I also encountered this issue: every time the page gains focus, it triggers calls to the session. To work around this, I set refetchOnWindowFocus to false like this:

 <SessionProvider session={session} refetchOnWindowFocus={false}>
        {children}
 </SessionProvider>

However, I haven’t fully explored whether this workaround could cause any side effects or regressions in the app.

We can also add an refetchInterval. Check out the SessionProviderProps for more details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Ask how to do something or how something works
Projects
None yet
Development

No branches or pull requests