What could be causing the malfunction of my token rotation feature in nextAuth?

I am developing a web application that involves working with an external API alongside my team member. We are making API requests using Next.js. I have implemented nextAuth for authentication, but I am facing issues with token rotation. After successful login, the user receives an Access Token and Refresh Token from our backend. The Access Token expires in 10 minutes while the refresh token has a longer lifespan. I store the Access Token in the nextAuth session for app-wide access. Whenever the user makes an API request, I include the access token to facilitate the request using Axios. To handle expired Access Tokens, I have set up an axios interceptor to automatically request a new Access Token as long as the refresh token is valid. This process works well, generating new access and refresh tokens when needed. However, nextAuth still attaches the old and expired access token to the API requests, leading to request failures. I'm unsure about what I might be doing wrong as this is my first experience with Next.js. We are considering abandoning nextAuth in favor of implementing our custom solution. Our proposed approach involves obtaining the access token and refresh token, storing the refresh token in redis, passing the refresh token as an HTTP-only cookie, saving the user's sessionId in local storage for persistent login, storing the access token in-memory, and attaching it to every API call. We are uncertain if this alternative method is robust enough or if we should persist in trying to make nextAuth work. Your guidance on this matter would be greatly appreciated.

Here are snippets of some of my code:

`const authOptions: NextAuthOptions = {
     session: {
    strategy: "jwt",
  },

  providers:[
    CredentialsProvider({
        type: "credentials",
        credentials: {},
       async authorize(credentials, req){
            const { username, password } = credentials as {
                username: string;
                password: string;
              };

              if(!credentials){
                return null
              }
              const userData = {
                username: username,
                password: password
              }
            

              try {
                
                const response = await axios.post(`${BASE_URL}/login`, userData);
                if(response?.data){
                  
                  const user: jwtDecodedAttributes = jwt_decode(response?.data?.userToken);
                  
                  return {
                    id: user.userId,
                    name: user.username,
                    role: user.role,
                    profilepicture: user.profilepicture,
                    iat: user?.iat,
                    exp: user?.exp,
                    username: user?.username,
                    token: response?.data?.userToken,
                    email: user.email,
                    userId: user?.userId,
                    refresh: response?.data.userRefreshToken
                  };
            }
             
              } catch (error) {
                
              }
              return null
        }
    })
  ],

  pages: {
    signIn: "/login",
  },

callbacks:{
 async jwt({token, user, trigger, session}){
  if (trigger === "update") {
    return { ...token, ...session.user };
  }

      return {...token, ...user} ;
  },

 async session({session, token, user}){
    
    session.user = token
   
    
    return session
 }
}
};


 export default NextAuth(authOptions);`

The function to handle refreshing the token:

export const RefreshFunction = () => {
         const { status, data, update } = useSession();

    const refreshToken = async() => {
  const getItem = localStorage.getItem('refresh');
  const refreshData = {
    refresh: getItem,
   
  }
   
   if(data?.user.refresh){
    try {
      const res = await axios.post<axiosGetRefreshTokenAttributes>(`${BASE_URL}/refresh`, refreshData);

      const accessToken = res?.data?.userToken
      const refreshToken = res?.data?.refreshToken
      localStorage.removeItem('refresh');
     
      if(data?.user){
        await update({
          ...data,
          user:{
            ...data?.user,
            token: accessToken,
            refresh: refreshToken

          }
          
        })
       
      }
      localStorage.setItem('refresh', refreshToken as string)
     
    } catch (error) {
      return error
    }
   }

  }

   return refreshToken
}; 

The Axios interceptor function:

const useAxiosPrivate = () => {
   const { status, data } = useSession();
   const refreshToken = RefreshFunction()

useEffect(() => {
    const requestInterceptors = axiosPrivate.interceptors.request.use(
        config => {
            if(!config.headers['authorization']){
                config.headers['authorization'] = `Bearer ${data?.user?.token}`
            }
            return config;
        }, (error) => Promise.reject(error)
    );

    const responseIntercept = axiosPrivate.interceptors.response.use(
        response => response,
        async (error)=>{
            const prevRequest = error?.config;
            console.log(error.response.status, 'kkk')
            if(error?.response.status === 401 && !prevRequest?.sent){
                prevRequest.sent = true;
               
                const token = await refreshToken()
                 prevRequest.headers['authorization'] = `Bearer ${token}`;
                
                 return axiosPrivate(prevRequest);
               
            }
            return Promise.reject(error);
        }
    );

    return() =>{
        axiosPrivate.interceptors.request.eject(requestInterceptors)
        axiosPrivate.interceptors.response.eject(responseIntercept)
    }

}, [data?.user?.token])

return axiosPrivate;
};

export default useAxiosPrivate;

An example of an API call:

const { status, data } = useSession();

 await axiosPrivate.post(`/createcompany`, companyData,  
 {withCredentials: true, headers: {Authorization: `
 ${data?.user.token}`}});

Despite the refresh endpoints successfully generating new access and refresh tokens upon expiry detection, the new access token fails to get attached to the nextAuth session. The error message indicates that my access token has expired, implying that the newly generated access token does not get properly linked.

Answer №1

Unfortunately, Next-Auth is currently just a personal project and not suitable for production use. The lack of built-in refresh token logic is a clear indication of this limitation.

If you're interested in implementing refresh tokens with Next Auth, the limited documentation can be found here: https://next-auth.js.org/v3/tutorials/refresh-token-rotation

It appears that adding a date check to refresh expired access tokens is necessary. However, be prepared for various challenges and bugs both during development and deployment when attempting to integrate this feature.

    async function refreshAccessToken(token) {
  ... // Code snippet for refreshing access token
}

In addition, consider the callback functions...

async jwt(token, user, account) {
  ... // Callback function to manage access tokens
},

It's recommended to use Next Auth as-is without trying to force features like refresh tokens that are not natively supported. Trying to customize frameworks beyond their design can lead to complex issues and frustrations.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Adding a simulated $state object to an angular unit test

I'm facing some challenges with Angular unit testing as I am not very proficient in it. Specifically, I am struggling to set up a simple unit test. Here is my Class: class CampaignController { constructor($state) { this.$state = $state; ...

Express server continuously returning 404 error for all requests originating from the index page

Experiencing a challenge with the express/gulpfile.js code. Express can serve the index.html without issue, but it fails to navigate to the lib/* directory resulting in all requests for resources at the top of my index.html returning 404 errors. Browser-s ...

Unable to import necessary modules within my React TypeScript project

I am currently building a React/Express application with TypeScript. While I'm not very familiar with it, I've decided to use it to expand my knowledge. However, I've encountered an issue when trying to import one component into another comp ...

Building a Next.js application in a docker container on a local machine using minikube is successful, however, the build fails when attempting to build it on a staging environment

I've encountered a puzzling issue. My next.js app is running in a docker container. It builds successfully on my local Ubuntu machine with minikube and ks8 orchestration, but it gets stuck indefinitely during the build process on the staging setup. T ...

In my Django html file, I am facing an issue with the IF statement that seems to be dysfunctional

I have a like button and I want it to display an already liked button if the user has already liked the post. Here is my HTML: {% for post in posts %} <div class="border-solid border-2 mr-10 ml-10 mt-3 px-2 pb-4"> & ...

Is there a way to transform authorid into postid in order to retrieve author information and store it in my authorDocument array?

**Can you help me troubleshoot why this code is not functioning properly? ** let posts = await postsCollection.aggregate([ {$match: {_id: new ObjectID(id)}}, {$addFields: {authorId: { $toObjectId: "$author"}}}, {$lookup: {from: "user ...

in javascript, how can you access the value of a parent object within a nested object?

Within the material ui framework, I am utilizing the createMuiTheme function to create a theme. How can I access the values of the parent object within a nested object? (I am aware that I can declare global variables above the function); To better unders ...

The challenges with implementing makeStyles in React Material UI

const useStyles = makeStyles((theme) => ({ toolbarMargin: { ...theme.mixins.toolbar, marginBottom: "3em", }, logo: { height: "7em", }, tabContainer: { marginLeft: "auto", }, tab: { ...theme ...

The electron program is unable to locate the package.json module

I am new to electron and attempting to run an express app for the first time. However, I encountered this error: Need assistance updating code Error: Cannot find module 'C:\package.json' at Module._resolveFilename (module.js:440:15) ...

Using command line arguments to pass parameters to package.json

"scripts": { "start": "gulp", ... }, I have a specific npm package that I'm using which requires passing parameters to the start command. Can anyone help me with how to pass these parameters in the command line? For example, is it possible ...

Sharing the checkbox's checked status with an AJAX script

I am faced with a challenge involving a table that contains checkboxes in the first column. When a checkbox is checked, it triggers an AJAX script that updates a PHP session variable with the selected values. The functionality is currently operational, but ...

Retrieve a specific nested key using its name

I am working with the following structure: const config = { modules: [ { debug: true }, { test: false } ] } My goal is to create a function that can provide the status of a specific module. For example: getStatus("debug") While I can access the array ...

Ways to remove items from Vuex store by utilizing a dynamic path as payload for a mutation

I am looking to implement a mutation in Vuex that dynamically updates the state by specifying a path to the object from which I want to remove an element, along with the key of the element. Triggering the action deleteOption(path, key) { this.$store.d ...

Concealing option value based on ng-if conditions in AngularJS: A guide

I am designing an Input form with two input fields that need to be compared and values displayed if a certain condition is met. The input fields are: <td class="td_label"><label>Client Age :</label></td> <td class="td_input"> ...

Show an HTML image encoded in base64 from its origin

Is there a way to embed a base64 image in HTML without having to paste the entire code directly into the file? I'm looking for a more efficient method. For example: <div> <p>Image sourced from an online repository</p> <img src=" ...

Retrieve specific components of objects using a GET request

When visitors land on my web app's homepage, a GET request is triggered to fetch a current list of Stadiums stored in the database through my API. However, the Stadium objects retrieved are packed with unnecessary data, particularly extensive arrays o ...

Converting basic CSS to makeStyles for React: A step-by-step guide

Looking to convert basic CSS into makeStyles for React? I've set up some styling on a TextField to allow vertical stretching using the resize attribute. It's currently functioning as expected. Here's the original code that needs some restru ...

Unable to set value using React Hook Form for Material-UI Autocomplete is not functioning

Currently, I am in the process of constructing a form using React-Hook-Form paired with Material-UI. To ensure seamless integration, each Material-UI form component is encapsulated within a react-hook-form Controller component. One interesting feature I a ...

The directory "Users/node_modules/crypto-browserify" is not a valid directory in react

After discovering the package crypto-browserify on GitHub, I attempted to integrate it into my React Native project. Following the command npm install --save crypto-browserify, I then imported it using var crypto = require('crypto-browserify');. ...

Revise a function that locates an object with a corresponding id in an array, modifies a property, and updates a React component's state

Here is a function that takes in a rating value within an object. The ID and Question properties remain unchanged, but the rating value can be updated. This will result in updating a React state value. Is there a method to improve the readability and con ...