base refresh token bug fix
This commit is contained in:
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user