next-auth token is not persisting after refreshing token

i am currently working on a nuxt application, which i need to integrate with a custome provider for authentication(OAuth). for authentication I am using next-auth(AuthJs), All is working fine, when it comes to refreshing the access_token which is necessary for this application it fails. The validity of access_token is 2 hours

in the jwt callback, after assigning the refreshed_token to the token and passed to the sesssion callback. But in the next session check(which automatically runs) the token in the jwt callback has the older values. The assigned values of initial sign in is persisted. but as for the refreshing it becomes an issue.

As the value is not re-assigned with the refresh_token so is the expire_at attribute. So it will keep refreshing the token until it makes the initial refresh_token invalid.

Is there any workaround/solutions to this problem?

why the values(returned) in the initial login (which is checking for an account param in the callback) working well and the token is being persisted?

server/api/auth/[...].ts

import type { AuthConfig, TokenSet } from "@auth/core/types";
import { NuxtAuthHandler } from "#auth";
import { JWT } from "@auth/core/jwt";

const runtimeConfig = useRuntimeConfig();
const BASE_URL = "Base url";

async function refreshAccessToken(token: JWT): Promise<TokenSet> {
  const params = new URLSearchParams({
    client_id: process.env.NUXT_AUTH_CLIENT_ID!,
    client_secret: process.env.NUXT_AUTH_CLIENT_SECRET!,
    grant_type: "refresh_token",
    refresh_token: token.refresh_token as string,
  });
  const response = await fetch(`${BASE_URL}/oauth/token`, {
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: params.toString(),
    method: "POST",
  });
  const tokens: TokenSet = await response.json();

  if (!response.ok) {
    throw tokens;
  }
  return {
    ...token,
    access_token: tokens.access_token,
    refresh_token: tokens.refresh_token,
    error: null,
    expires_at: Math.floor(Date.now() / 1000 + 30), // 30 for testing
    // expires_at: Math.floor(Date.now() / 1000 + tokens.expires_in!),
  };
}

export const authOptions: AuthConfig = {
  secret: runtimeConfig.authJs.secret,
  debug: true,
  callbacks: {
    async jwt({ token, account }) {
      console.log('-----------------------------------');
      
      console.log("token in jwt is ", token);

      const now = Date.now();
      let resToken = null;
      if (account) {
        console.log("initial login", token, account);

        // Initial login
        resToken = {
          ...token,
          access_token: account.access_token,
          refresh_token: account.refresh_token,
          expires_at: Math.floor(Date.now() / 1000 + 30),
          // expires_at: Math.floor(Date.now() / 1000 + account.expires_in!),
          error: null,
        };
      } else if (now < (token.expires_at as number) * 1000) {
        console.log("token is valid ", token);

        // Have valid token
        resToken = token;
      } else {
        // RefreshToken
        try {
          console.log("before refresh token is ", token);
          // the returned refreshed token is not persisting so expires_at, access_token and refresh_token remains the same as the first
          // So this code blocks keeps running for around 3 times as the expires_at not changed
          // it will invalidate the accessToken and refresh token it becomes a "RefreshAccessTokenError"
          resToken = await refreshAccessToken(token);
          console.log("resToken is ", resToken);
          console.log('-----------------------------------');
          
        } catch (error) {
          console.error("Error refreshing access token", error);
          return { ...token, error: "RefreshAccessTokenError" as const };
        }
      }
      return resToken;
    },
    async session({ session, token }) {
      return {
        ...session,
        access_token: token.access_token,
        refresh_token: token.refresh_token,
        error: token.error,
      };
    },
  },

  providers: [
    {
      id: "projectId",
      name: "projectName",
      type: "oauth",
      issuer: BASE_URL,
      clientId: process.env.NUXT_AUTH_CLIENT_ID,
      clientSecret: process.env.NUXT_AUTH_CLIENT_SECRET,
      token: `${BASE_URL}/oauth/token`,
      authorization: {
        url: `${BASE_URL}/oauth/authorize`,
        params: { scope: "api_v3" },
      },
      redirectProxyUrl: "http://localhost:3000/api/auth",
      userinfo: `${BASE_URL}/api/v3/users/me`,
    },
  ],
};

export default NuxtAuthHandler(authOptions, runtimeConfig);

Leave a Comment