14

I have implemented a next-auth authentication system for my Next.js app. In the providers, I have chosen credentials because I have a node.js backend server.

The problem that I am facing is the expiration of next auth session is not in sync up with the expiration of jwt token on my backend. This is leading to inconsistency. Kindly help me out.

Below is my next auth code

import NextAuth, {
  NextAuthOptions,
  Session,
  SessionStrategy,
  User,
} from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { login } from "@actions/auth";
import { toast } from "react-toastify";
import { JWT } from "next-auth/jwt";
import { NextApiRequest, NextApiResponse } from "next";
import { SessionToken } from "next-auth/core/lib/cookie";

// For more information on each option (and a full list of options) go to
// https://next-auth.js.org/configuration/options
const nextAuthOptions = (req: NextApiRequest, res: NextApiResponse) => {
  return {
    providers: [
      CredentialsProvider({
        name: "Credentials",
        credentials: {
          email: { label: "Email", type: "text" },
          password: { label: "Password", type: "password" },
        },
        async authorize(
          credentials: Record<"email" | "password", string> | undefined,
          req
        ): Promise<Omit<User, "id"> | { id?: string | undefined } | null> {
          // Add logic here to look up the user from the credentials supplied
          const response = await login(
            credentials?.email!,
            credentials?.password!
          );
          const cookies = response.headers["set-cookie"];

          res.setHeader("Set-Cookie", cookies);
          if (response) {
            var user = { token: response.data.token, data: response.data.user };
            return user;
          } else {
            return null;
          }
        },
      }),
    ],
    refetchInterval: 1 * 24 * 60 * 60,
    secret: process.env.NEXTAUTH_SECRET,
    debug: true,
    session: {
      strategy: "jwt" as SessionStrategy,
      maxAge: 3 * 24 * 60 * 60,
    },
    jwt: {
      maxAge: 3 * 24 * 60 * 60,
    },
    callbacks: {
      jwt: async ({ token, user }: { token: JWT; user?: User }) => {
        user && (token.accessToken = user.token);
        user && (token.user = user.data);
        return token;
      },
      session: async ({ session, token }: { session: Session; token: JWT }) => {
        session.user = token.user;
        session.accessToken = token.accessToken;
        return session;
      },
    },
  };
};
export default (req: NextApiRequest, res: NextApiResponse) => {
  return NextAuth(req, res, nextAuthOptions(req, res));
};
Raghav
  • 161
  • 1
  • 2
  • 11

3 Answers3

3

In your options, there is the maxAge property. Set it to be equal to whatever time you have set in your backend server. The time is in seconds, so yours is currently set to 3days.

See here

Youzef
  • 616
  • 1
  • 6
  • 23
  • 2
    maxAge: Seconds - How long until an idle session expires and is no longer valid. That wont work, if the user isn't idle. – fieldju Jan 24 '23 at 16:02
2

I have a similar setup: NextAuth (version 4) with Next.js (version 13 with App Router) on the client using credential authentication with a jwt and a separate backend session token.

This is how we keep the sessions in sync:

  1. As others mentioned, in the NextAuthOptions, set the maxAge property to the same expiration time as the token on the back end server.

    const nextAuthOptions = {
      providers: [...],
      session: {
        strategy: 'jwt',
        maxAge: 4 * 60 * 60 // 4 hours
      },
      ...
    }
    
  2. At the top level of the route tree for your authenticated pages, check if your client-side session is about to expire and if so, refresh the token. I refresh the token on the client-side with the NextAuth useSession update function and send a request to the backend API to update the token expiration on the server. This is added to the layout.tsx file in the top level hierarchy for any views that should be authenticated to see.

    --layout.tsx--

    'use client';
    import { useSession } from 'next-auth/react';
    
    export default function Layout() {
      const { data: session, status, update } = useSession();
    
      useEffect(() => {
        const interval = setInterval(() => {
          update(); // extend client session
          // TODO request token refresh from server
        }, 1000 * 60 * 60)
        return () => clearInterval(interval)
      }, [update]); 
      return (
        {children}
      )
    }
    
  3. If you also want to add functionality to determine if the user is idle or not before extending their session, you can use react-idle-timer.

    --full layout.tsx file--

    'use client';
    import React, { useEffect } from 'react';
    import { useSession, signOut } from 'next-auth/react';
    import { useIdleTimer } from 'react-idle-timer';
    
    export default function Layout({ children }: { children: React.ReactNode 
    }) {
      const { data: session, status, update } = useSession();
      const CHECK_SESSION_EXP_TIME = 300000; // 5 mins
      const SESSION_IDLE_TIME = 300000; // 5 mins 
      const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL;
    
      const onUserIdle = () => {
        console.log('IDLE');
      };
    
      const onUserActive = () => {
        console.log('ACTIVE');
      };
    
      const { isIdle } = useIdleTimer({
        onIdle: onUserIdle,
        onActive: onUserActive,
        timeout: SESSION_IDLE_TIME, //milliseconds
        throttle: 500
      });
    
      useEffect(() => {
        const checkUserSession = setInterval(() => {
          const expiresTimeTimestamp = Math.floor(new Date(session?.expires || '').getTime());
          const currentTimestamp = Date.now();
          const timeRemaining = expiresTimeTimestamp - currentTimestamp;
    
          // If the user session will expire before the next session check
          // and the user is not idle, then we want to refresh the session
          // on the client and request a token refresh on the backend
          if (!isIdle() && timeRemaining < CHECK_SESSION_EXP_TIME) {
            update(); // extend the client session
    
            // request refresh of backend token here
    
          } else if (timeRemaining < 0) {
            // session has expired, logout the user and display session expiration message
            signOut({ callbackUrl: BASE_URL + '/login?error=SessionExpired' });
          }
        }, CHECK_SESSION_EXP_TIME);
    
        return () => {
          clearInterval(checkUserSession);
        };
      }, [update]); 
      return (
        <main>
          {children}
        </main>
      );
    }
    
danilibros
  • 75
  • 1
  • 9
-1

I found a solution you can try to save the jwt cookie inside session token and read it from there and they will expiry both at the same time check this out for more info https://github.com/nextauthjs/next-auth/discussions/1290

Zakkkk
  • 1
  • 1
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jul 17 '23 at 07:00