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 is not renewed on client side right after SSR state changes #2129

Closed
HarunKilic opened this issue Jun 4, 2021 · 6 comments
Closed
Labels
bug Something isn't working

Comments

@HarunKilic
Copy link

Description 🐜

The problem is described here: #2123 (comment)

Seems like when changing the session at server side, the client will still use the old session states.
This leads to false informations at the client side.

How to reproduce ☕️

https://codesandbox.io/s/4gokt?file=/pages/_app.tsx

Changes is at [...nextauth], _app and client.
_app has getinitialprops, which uses the getSession method.
It will at the server side and client side use the same state, but i expect the client side to use new the new state.

Environment 🖥

npx: installed 1 in 1.676s

  System:
    OS: macOS 11.4
    CPU: (16) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
    Memory: 854.63 MB / 16.00 GB
    Shell: 5.8 - /bin/zsh
  Binaries:
    Node: 14.16.0 - ~/.nvm/versions/node/v14.16.0/bin/node
    Yarn: 1.22.10 - /usr/local/bin/yarn
    npm: 6.14.11 - ~/.nvm/versions/node/v14.16.0/bin/npm
    Watchman: 4.9.0 - /usr/local/bin/watchman
  Browsers:
    Chrome: 91.0.4472.77
    Safari: 14.1.1
@HarunKilic HarunKilic added the bug Something isn't working label Jun 4, 2021
@mahieyin-rahmun
Copy link
Contributor

mahieyin-rahmun commented Jun 4, 2021

If I understood correctly, this is a known issue (#596). Now, my circumstances are different because I am not using SSR, but I am leaving this here for discussion and possibly help you guide towards a direction:

I also ran into this issue and got around it by using the approach mentioned here as inspiration.

hooks/useAuth.tsx

const sessionUrl = "/api/auth/session";

async function fetchSession(url: string) {
  const response = await fetch(url);

  if (!response.ok) {
    throw new Error(`Could not fetch session from ${url}`);
  }

  const session: Session = await response.json();

  if (!session || Object.keys(session).length === 0) {
    return null;
  }

  return session;
}

// useSwr from `swr` package: yarn add swr
export function useAuth(refreshInterval?: number) {
  /*
    custom hook that keeps the session up-to-date by refreshing it

    @param {number} refreshInterval: The refresh/polling interval in seconds. default is 20.
    @return {tuple} A tuple of the Session and boolean
  */
  const { data, error } = useSwr(sessionUrl, fetchSession, {
    revalidateOnFocus: true,
    revalidateOnMount: true,
    revalidateOnReconnect: true,
  });

  useEffect(() => {
    const intervalId = setInterval(
      () => mutate(sessionUrl),
      (refreshInterval || 20) * 1000,
    );

    return () => clearInterval(intervalId);
  }, []);

  return {
    session: data,
    loading: typeof data === "undefined" && typeof error === "undefined",
  };
}

I made a custom HOC for protecting the pages which uses the above hook:

hocs/withAuth.tsx

import { Session } from "next-auth";
import { signIn } from "next-auth/client";
import React from "react";
import { useAuth } from "./Hooks";

type TSessionProps = {
  session: Session;
};

export function withAuth<P extends object>(refreshInterval?: number) {
  /*
    @param { number } refreshInterval: number of seconds before each refresh
  */
  return function (Component: React.ComponentType<P>) {
    return function (props: Exclude<P, TSessionProps>) {
      const { session, loading } = useAuth(refreshInterval);

      if (typeof window !== "undefined" && loading) {
        return null;
      }

      if (!loading && !session) {
        // or return a custom <AccessDenied /> component or redirect to `/login`.
        return (
          <>
            Not signed in <br />
            <button onClick={() => signIn()}>Sign in</button>
            <pre>{"User is not logged in"}</pre>
          </>
        );
      }

      return <Component session={session} {...props} />;
    };
  };
}

And then I am protecting the pages required with the above HOC.

e.g. pages/profile.tsx

export default withAuth(5 * 60)(Profile); // refresh every 5 minutes, should be less than your token expiration time

@balazsorban44
Copy link
Member

I think the best solution is to trigger the token refresh from client-side, as it's usually the client that will trigger an API call. Then deduplication of these requests would make sure that the same refresh_token isn't used multiple times.

@HarunKilic
Copy link
Author

I think the best solution is to trigger the token refresh from client-side, as it's usually the client that will trigger an API call. Then deduplication of these requests would make sure that the same refresh_token isn't used multiple times.

I just find it a bit bad practice.

@HarunKilic
Copy link
Author

@mahieyin-rahmun This is only a way out for client side. The problem is the state between client side and server side. Not being able to renew client side state after server side rendering, is giving problems.

@themhassany
Copy link

I have the same issue with credential provider. to address the concurrency issue, i implemented a simple rotation solution that will keep the old refresh token valid for a few seconds and return the newly generated token on concurrent requests. every thing works fine even on concurrent rotation requests.

but when the user navigate back and forth through the browser history the next-auth will send the old token even it has just received a new token (multiple times concurrently).

@ThangHuuVu
Copy link
Member

ThangHuuVu commented Mar 25, 2023

Hi all, let's continue the discussion here, I just added our latest update: #3941 (comment) I'm closing this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants