numanu
numanu

Reputation: 11

NextAuth refreshes the whole page when session is updated

I created the following react hook to handle access tokens when they expired. Simply it checks the token before sending the request and refreshes and updates next auth session if expired.

const useAxiosAuth = () = >{
    const axiosAuth = axios.create({
        baseURL: "http://localhost:8080",
        headers: {
            "Content-Type": "application/json",
            "Access-Control-Allow-Origin": "*",
        },
    });

    const {
        data: session,
        status,
        update
    } = useSession();
    const router = useRouter();
    axiosAuth.interceptors.request.use(async(req) = >{
        req.headers.Authorization = `Bearer $ {
            session ? .user.access_token
        }`;
        const decoded = jwtDecode(session ! .user.access_token) as any;
        const isExpired = dayjs.unix(decoded.exp).diff(dayjs()) < 1;
        if (!isExpired) {
            return req;
        }
        await axios.post("http://localhost:8080/auth-service/auth/refresh-token", {
            refreshToken: session ? .user.refresh_token,
        }).then(async(res) = >{
            await update({...session,
                user: {...session ? .user,
                    access_token: res.data.access_token,
                    refresh_token: res.data.refresh_token,
                },
            });
            if (session) {
                session.user.access_token = res.data.access_token;
                session.user.refresh_expires_in = res.data.refresh_token;
            }
        }).
        catch((error) = >{
            router.replace(process.env.NEXT_PUBLIC_URL + "/auth/login");
        });

        req.headers.Authorization = `Bearer $ {
            session ? .user.access_token
        }`;
        return req;
    },
    (error) = >Promise.reject(error));

    return axiosAuth;
};

My [...nextauth].ts file:

import axios from "@/app/lib/axios";
import {
    getCookie
}
from "cookies-next";
import {
    jwtDecode
}
from "jwt-decode";
import {
    NextApiRequest,
    NextApiResponse
}
from "next";
import NextAuth from "next-auth/next";
import CredentialsProvider from "next-auth/providers/credentials";

export
default async
    function auth(req:
    NextApiRequest, res: NextApiResponse) {
        const rememberMeCookie = getCookie("remember-me", {
            req,
            res
        });

        let maxAge = rememberMeCookie === "true" ? 7 * 24 * 60 * 60 : 10 * 60;

        return await NextAuth(req, res, {
            providers: [CredentialsProvider({
                name: "Credentials",
                credentials: {
                    username: {
                        label: "Username",
                        type: "text",
                        placeholder: "username",
                    },
                    password: {
                        label: "Password",
                        type: "password"
                    },
                },
                async authorize(credentials, req) {
                    const res = await axios.post("/auth-service/auth/login", {
                        username: credentials ? .username,
                        password: credentials ? .password,
                    });

                    const user = res.data;

                    if (user) {
                        return user;
                    } else {
                        return null;
                    }
                },
            }), ],
            callbacks: {
                async jwt({
                    token,
                    user,
                    trigger,
                    session
                }: any) {

                    if (trigger === "update") {
                        return {...token,
                            ...session.user
                        }
                    }
                    return {...token,
                        ...user
                    };
                },
                async session({
                    session,
                    token,
                    user
                }) {
                    session.user = token as any;
                    const decoded = jwtDecode(session.user.access_token) as any;
                    session.user.id = decoded.uid;
                    session.user.name = decoded.given_name;
                    session.user.email = decoded.email;
                    session.user.role = decoded.realm_access.roles[0];
                    session.user.username = decoded.preferred_username;
                    if (!session.user.session_expiry) {
                        const exp = new Date(new Date().getTime() + maxAge * 1000).toISOString();
                        session.user.session_expiry = exp;
                    }

                    return session;
                },
                async redirect({
                    url,
                    baseUrl
                }) {
                    return baseUrl + "/admin/home";
                },
            },
            pages: {
                signIn: "/auth/login",
            },
            session: {
                strategy: "jwt",
                maxAge,
                updateAge: 0
            },
            jwt: {
                maxAge,
            },
            secret: process.env.NEXT_PUBLIC_NEXTAUTH_SECRET,
        });
    }

All of these working fine.

But when i try to use provider like following to check if session expired or not, nextauth refreshes the whole page. It updates the session but I lose all the page state since it refreshes it.


import {
    useSession
}
from "next-auth/react";
import {
    useRouter
}
from "next/navigation";
import {
    ProgressSpinner
}
from "primereact/progressspinner";
interface Props {
    children: React.ReactNode;
}

const Auth = ({
    children
}: Props) = >{
    const router = useRouter();
    const {
        data: sesion,
        status
    } = useSession({
        required: true,
        onUnauthenticated() {
            router.push("/auth/login");
        },
    });

    if (status === "loading") {
        return ( < div className = "w-screen h-screen flex justify-center items-center" > <ProgressSpinner style = {
            {
                width: "50px",
                height: "50px"
            }
        }
        strokeWidth = "8"fill = "var(--surface-ground)"animationDuration = "1s" / ></div>
    );
  }
  return <>{children}</ > ;
    };

    export
default Auth;

When I use this component in top of my application like this , it happens.

<SessionProvider refetchInterval={5 * 60} refetchOnWindowFocus={true}>
      <Auth>{children}</Auth>
    </SessionProvider>

How to prevent this behaviour? I am not experienced with next.js much. So my codes may contain many mistakes. The versions that I am using are:

"next": "14.0.1"
"next-auth": "^4.24.5"

Upvotes: 0

Views: 559

Answers (1)

didikee
didikee

Reputation: 447

I also encountered the same problem, the version I used was 4.23.1 I use simulated events to trigger updates so that I don't refresh the entire page.

This method is rather hacky and may have compatibility issues in later versions.

const reloadSession = () => {
const event = new Event("visibilitychange");
document.dispatchEvent(event);

};

link: Next-auth - How to update the session client side?

Upvotes: 0

Related Questions