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 = {
|
export type RefreshTokenRequestModel = {
|
||||||
token: string,
|
token: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const refreshToken = async (model: RefreshTokenRequestModel) => {
|
export const refreshToken = async (model: RefreshTokenRequestModel) => {
|
||||||
const res = await api.post('/token', model)
|
const res = await axios.post(
|
||||||
return res.data
|
`${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 axios from "axios";
|
||||||
|
import {
|
||||||
|
getToken,
|
||||||
|
getRefreshToken,
|
||||||
|
setToken,
|
||||||
|
setRefreshToken,
|
||||||
|
removeToken,
|
||||||
|
removeRefreshToken,
|
||||||
|
} from "./func";
|
||||||
import { refreshToken } from "./auth/refresh-token";
|
import { refreshToken } from "./auth/refresh-token";
|
||||||
|
|
||||||
// Create axios instance
|
declare global {
|
||||||
const api = axios.create({
|
interface Window {
|
||||||
|
isRefreshingToken?: boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const axiosInstance = axios.create({
|
||||||
baseURL: process.env.NEXT_PUBLIC_API_BASE_URL,
|
baseURL: process.env.NEXT_PUBLIC_API_BASE_URL,
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
});
|
});
|
||||||
|
|
||||||
let isRefreshing = false;
|
axiosInstance.interceptors.response.use(
|
||||||
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(
|
|
||||||
(response) => response,
|
(response) => response,
|
||||||
async (error) => {
|
async (error) => {
|
||||||
const originalRequest = error.config;
|
const originalRequest = error.config;
|
||||||
|
|
||||||
if (
|
if (error?.response?.status === 401 && !originalRequest._retry) {
|
||||||
error.response?.status === 401 &&
|
if (typeof window !== "undefined" && window.isRefreshingToken) {
|
||||||
!originalRequest._retry // custom flag to prevent infinite loops
|
// Wait for the refresh to complete
|
||||||
) {
|
await new Promise((resolve) => {
|
||||||
if (isRefreshing) {
|
const checkComplete = setInterval(() => {
|
||||||
// Queue the requests while refreshing
|
if (typeof window !== "undefined" && !window.isRefreshingToken) {
|
||||||
return new Promise((resolve, reject) => {
|
clearInterval(checkComplete);
|
||||||
failedQueue.push({ resolve, reject });
|
resolve(true);
|
||||||
})
|
}
|
||||||
.then((token) => {
|
}, 100);
|
||||||
originalRequest.headers.Authorization = `Bearer ${token}`;
|
});
|
||||||
return api(originalRequest);
|
|
||||||
})
|
return axiosInstance(originalRequest);
|
||||||
.catch((err) => {
|
|
||||||
return Promise.reject(err);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
originalRequest._retry = true;
|
originalRequest._retry = true;
|
||||||
isRefreshing = true;
|
if (typeof window !== "undefined") {
|
||||||
|
window.isRefreshingToken = true;
|
||||||
const token = useAuthStore.getState().user?.refreshToken;
|
|
||||||
console.log(useAuthStore.getState());
|
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
useAuthStore.getState().logout();
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await refreshToken({ token });
|
const refreshTokenValue = await getRefreshToken();
|
||||||
// Update tokens in Zustand store
|
|
||||||
useAuthStore.getState().login({
|
if (!refreshTokenValue) {
|
||||||
...useAuthStore.getState().user!,
|
await removeRefreshToken();
|
||||||
accessToken: data.accessToken,
|
await removeToken();
|
||||||
refreshToken: data.refreshToken || token,
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await refreshToken({
|
||||||
|
token: refreshTokenValue,
|
||||||
});
|
});
|
||||||
|
|
||||||
api.defaults.headers.common.Authorization = `Bearer ${data.accessToken}`;
|
// Support both response structures
|
||||||
originalRequest.headers.Authorization = `Bearer ${data.accessToken}`;
|
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);
|
if (refreshTokenValueNew) {
|
||||||
} catch (err) {
|
await setRefreshToken(refreshTokenValueNew);
|
||||||
processQueue(err, null);
|
}
|
||||||
useAuthStore.getState().logout();
|
|
||||||
return Promise.reject(err);
|
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 {
|
} 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