base refresh token bug fix

This commit is contained in:
hamid zarghami
2025-12-01 09:31:04 +03:30
parent e02ab7fb3f
commit 65679c1b2a
3 changed files with 140 additions and 81 deletions
+12 -6
View File
@@ -1,10 +1,16 @@
import { api } from "../axiosInstance";
import axios from "axios";
export type RefreshTokenRequestModel = {
token: string,
}
token: string;
};
export const refreshToken = async (model: RefreshTokenRequestModel) => {
const res = await api.post('/token', model)
return res.data
}
const res = await axios.post(
`${process.env.NEXT_PUBLIC_API_BASE_URL}/public/auth/refresh`,
{ refreshToken: model.token },
{
headers: { "Content-Type": "application/json" },
}
);
return res.data;
};
+86 -75
View File
@@ -1,99 +1,101 @@
import { useAuthStore } from "@/zustand/authStore";
import axios from "axios";
import {
getToken,
getRefreshToken,
setToken,
setRefreshToken,
removeToken,
removeRefreshToken,
} from "./func";
import { refreshToken } from "./auth/refresh-token";
// Create axios instance
const api = axios.create({
declare global {
interface Window {
isRefreshingToken?: boolean;
}
}
const axiosInstance = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_BASE_URL,
headers: { "Content-Type": "application/json" },
});
let isRefreshing = false;
let failedQueue: {
resolve: (value?: unknown) => void;
reject: (error: unknown) => void;
}[] = [];
const processQueue = (error: unknown, token: string | null = null) => {
failedQueue.forEach(({ resolve, reject }) => {
if (error) {
reject(error);
} else {
resolve(token);
}
});
failedQueue = [];
};
// Attach access token to requests
api.interceptors.request.use((config) => {
const token = localStorage.getItem(
process.env.NEXT_PUBLIC_TOKEN_NAME as string
);
if (token) {
config.headers = config.headers || {};
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Response interceptor to handle 401 and refresh token logic
api.interceptors.response.use(
axiosInstance.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (
error.response?.status === 401 &&
!originalRequest._retry // custom flag to prevent infinite loops
) {
if (isRefreshing) {
// Queue the requests while refreshing
return new Promise((resolve, reject) => {
failedQueue.push({ resolve, reject });
})
.then((token) => {
originalRequest.headers.Authorization = `Bearer ${token}`;
return api(originalRequest);
})
.catch((err) => {
return Promise.reject(err);
});
if (error?.response?.status === 401 && !originalRequest._retry) {
if (typeof window !== "undefined" && window.isRefreshingToken) {
// Wait for the refresh to complete
await new Promise((resolve) => {
const checkComplete = setInterval(() => {
if (typeof window !== "undefined" && !window.isRefreshingToken) {
clearInterval(checkComplete);
resolve(true);
}
}, 100);
});
return axiosInstance(originalRequest);
}
originalRequest._retry = true;
isRefreshing = true;
const token = useAuthStore.getState().user?.refreshToken;
console.log(useAuthStore.getState());
if (!token) {
useAuthStore.getState().logout();
return Promise.reject(error);
if (typeof window !== "undefined") {
window.isRefreshingToken = true;
}
try {
const data = await refreshToken({ token });
// Update tokens in Zustand store
useAuthStore.getState().login({
...useAuthStore.getState().user!,
accessToken: data.accessToken,
refreshToken: data.refreshToken || token,
const refreshTokenValue = await getRefreshToken();
if (!refreshTokenValue) {
await removeRefreshToken();
await removeToken();
return;
}
const data = await refreshToken({
token: refreshTokenValue,
});
api.defaults.headers.common.Authorization = `Bearer ${data.accessToken}`;
originalRequest.headers.Authorization = `Bearer ${data.accessToken}`;
// Support both response structures
const accessToken =
data?.results?.data?.accessToken?.token ||
data?.data?.tokens?.accessToken?.token ||
data?.accessToken;
const refreshTokenValueNew =
data?.results?.data?.refreshToken?.token ||
data?.data?.tokens?.refreshToken?.token ||
data?.refreshToken;
processQueue(null, data.accessToken);
if (accessToken) {
await setToken(accessToken);
return api(originalRequest);
} catch (err) {
processQueue(err, null);
useAuthStore.getState().logout();
return Promise.reject(err);
if (refreshTokenValueNew) {
await setRefreshToken(refreshTokenValueNew);
}
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
return axiosInstance(originalRequest);
} else {
await removeRefreshToken();
await removeToken();
return;
}
} catch (refreshError) {
console.error("Token refresh failed:", refreshError);
await removeToken();
await removeRefreshToken();
if (typeof window !== "undefined") {
const pathname = window.location.pathname;
const slug = pathname.split("/")[1];
window.location.href = slug ? `/${slug}` : `/`;
}
return Promise.reject(refreshError);
} finally {
isRefreshing = false;
if (typeof window !== "undefined") {
window.isRefreshingToken = false;
}
}
}
@@ -101,4 +103,13 @@ api.interceptors.response.use(
}
);
export { api };
axiosInstance.interceptors.request.use(async function (config) {
const tokenValue = await getToken();
const token = tokenValue ? tokenValue : "";
config.headers.Authorization = "Bearer " + token;
return config;
});
export { axiosInstance as api };
+42
View File
@@ -0,0 +1,42 @@
export const getToken = async (): Promise<string | null> => {
if (typeof window === "undefined") return null;
return localStorage.getItem(
process.env.NEXT_PUBLIC_TOKEN_NAME as string
);
};
export const getRefreshToken = async (): Promise<string | null> => {
if (typeof window === "undefined") return null;
return localStorage.getItem(
process.env.NEXT_PUBLIC_REFRESH_TOKEN_NAME as string
);
};
export const setToken = async (token: string): Promise<void> => {
if (typeof window === "undefined") return;
localStorage.setItem(
process.env.NEXT_PUBLIC_TOKEN_NAME as string,
token
);
};
export const setRefreshToken = async (token: string): Promise<void> => {
if (typeof window === "undefined") return;
localStorage.setItem(
process.env.NEXT_PUBLIC_REFRESH_TOKEN_NAME as string,
token
);
};
export const removeToken = async (): Promise<void> => {
if (typeof window === "undefined") return;
localStorage.removeItem(process.env.NEXT_PUBLIC_TOKEN_NAME as string);
};
export const removeRefreshToken = async (): Promise<void> => {
if (typeof window === "undefined") return;
localStorage.removeItem(
process.env.NEXT_PUBLIC_REFRESH_TOKEN_NAME as string
);
};