I have a project made with NextJS 13.4, NextAuth, Axios and NestJS as the API server and authenticator.
I created hooks that will create Axios Intercpetors to handle the access and the refresh token logic. The hooks are working fine and I am able to send authorized requests to the API server using the access token and get a new token if the old one expired by using the refresh token. The tokens are stored inside the Session of the NextAuth and they get updated each time the Refresh Token is retrieved from the API server.
So far, I created the sign in and sign up functions without any issues and the Axios Interceptors and hooks are working fine.
Now, I started running into a BIG problem once I wanted to load the user profile once a component is loaded. I placed the axios call inside useEffect() so I will populate the userProfile state and then use it the component. When the page first load, the component is mounted fine and I can see the user data. But when I click Refresh in the browser or hit F5, I get an axios error of 401.
After some investigation, I found out that the hook is getting undefined session from NextAuth useSession. And since axios is getting the tokens from the session, it is getting rejected from the API since it is passing undefined in the headers.
When I moved the axios call outside the
useEffect()and tried to call it by pressing a button on the component, it works fine without an issue.
Something is happening when I palce the axios call inside the useEffect().
PLEASE help as my whole project is now stuck for days and I am not able to move forward. I searched the web daily for a solution but I was unable to.
Here are the different parts of the code that might help you help me.
The axios instance creation file:
import axios, { AxiosError } from "axios"
const BASE_URL = "http://localhost:8000" //API
export default axios.create({
baseURL: BASE_URL,
headers: { "Content-Type": "application/json" },
})
export const axiosAuth = axios.create({
baseURL: BASE_URL,
headers: { "Content-Type": "application/json" },
})
The useAxiosAuth hook file which will intercept the request and response to manage the tokens attachements:
"use client"
import { useEffect } from "react"
import { axiosAuth } from "lib/axios"
import { useSession } from "next-auth/react"
import { useRefreshToken } from "./useRefreshToken"
const useAxiosAuth = () => {
const { data: session, status } = useSession()
const refreshToken = useRefreshToken()
useEffect(() => {
const requestIntercept = axiosAuth.interceptors.request.use(
(config) => {
if (!config.headers["Authorization"]) {
config.headers[
"Authorization"
] = `Bearer ${session?.user?.accessToken}`
}
if (!config.headers["Token-Id"]) {
config.headers["Token-Id"] = `${session?.user?.tokenId}`
}
return config
},
(error) => Promise.reject(error)
)
const responseIntercept = axiosAuth.interceptors.response.use(
(response) => response,
async (error) => {
const prevRequest = error?.config
if (error?.response?.status === 401 && !prevRequest?.sent) {
prevRequest._retry = true
await refreshToken()
prevRequest.headers[
"Authorization"
] = `Bearer ${session?.user.accessToken}`
return axiosAuth(prevRequest)
}
return Promise.reject(error)
}
)
return () => {
axiosAuth.interceptors.request.eject(requestIntercept)
axiosAuth.interceptors.response.eject(responseIntercept)
}
}, [session, refreshToken, status])
return axiosAuth
}
export default useAxiosAuth
The useRefreshToken hook (used in the above file) that will get a new tokens using the previous Refresh Token and update the NextAuth session on the server and client:
"use client"
import axios from "lib/axios"
import { signIn, useSession } from "next-auth/react"
export const useRefreshToken = () => {
const { data: session, update, status } = useSession()
const refreshToken = async () => {
const res = await axios.post(
"/auth/refresh",
{},
{
headers: {
Authorization: `Bearer ${session?.user.refreshToken}`,
"Token-Id": session?.user.tokenId,
},
}
)
if (session) {
//update server session
await update({
...session,
user: {
...session.user,
accessToken: res?.data.accessToken,
refreshToken: res?.data.refreshToken,
tokenId: res?.data.tokenId,
accessTokenExpires: res?.data.accessTokenExpires,
},
})
//update client session at the same time
session.user.accessToken = res?.data.accessToken
session.user.refreshToken = res?.data.refreshToken
session.user.tokenId = res?.data.tokenId
session.user.accessTokenExpires = res?.data.accessTokenExpires
} else {
signIn()
}
}
return refreshToken
}
And finally, here is the implementation that is causing this strange issue:
"use client"
import React, { useEffect, useState } from "react"
import { useSession } from "next-auth/react"
import useAxiosAuth from "@/lib/hooks/useAxiosAuth"
import UserProfile from "@/types/user-profile"
export default function Profile() {
const [userProfile, setUserProfile] = useState<UserProfile>()
const axios = useAxiosAuth()
useEffect(() => {
;(async () => {
await axios.get<UserProfile>("/profile").then((res) => {
setUserProfile(res.data)
})
})()
return () => {}
}, [axios])
return (
<>
.....
</>
)
}
Here is the error I am getting from axios and you can see that the tokens are undefined
Please help and someone please guide me on what is wrong and how can I fix this.
