feat(auth): implement global logout callback for API interceptors
- Add global logout callback registration and retrieval functions in User context - Integrate logout callback in response interceptor to handle 401 errors on token refresh - Ensure fallback logout mechanism if callback is not registered, clearing cookies and redirecting to sign-in
This commit is contained in:
@@ -29,6 +29,17 @@ interface UserContextType {
|
||||
|
||||
const UserContext = createContext<UserContextType | undefined>(undefined);
|
||||
|
||||
// Global logout callback for use in non-React contexts (e.g., API interceptors)
|
||||
let globalLogoutCallback: (() => void) | null = null;
|
||||
|
||||
export const registerLogoutCallback = (callback: () => void) => {
|
||||
globalLogoutCallback = callback;
|
||||
};
|
||||
|
||||
export const getLogoutCallback = (): (() => void) | null => {
|
||||
return globalLogoutCallback;
|
||||
};
|
||||
|
||||
interface UserProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
@@ -47,6 +58,14 @@ export const UserProvider: React.FC<UserProviderProps> = ({ children }) => {
|
||||
Cookies.remove(COOKIE_KEYS.user);
|
||||
window.location.href = "/auth/sign-in";
|
||||
}, [router]);
|
||||
|
||||
// Register logout callback for use in API interceptors
|
||||
useEffect(() => {
|
||||
registerLogoutCallback(logout);
|
||||
return () => {
|
||||
globalLogoutCallback = null;
|
||||
};
|
||||
}, [logout]);
|
||||
useEffect(() => {
|
||||
try {
|
||||
const tokenFromCookie = Cookies.get(COOKIE_KEYS.access_token);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { AxiosError, AxiosResponse } from "axios";
|
||||
import Cookies from "js-cookie";
|
||||
import { toast } from "react-toastify";
|
||||
import { getLogoutCallback } from "../contexts/User.ctx";
|
||||
import { API_ENDPOINTS, api } from "../lib/api";
|
||||
import { COOKIE_KEYS } from "../lib/shared/cookies";
|
||||
|
||||
@@ -31,6 +32,27 @@ export const responseErrorHandler = async (error: AxiosError) => {
|
||||
API_ENDPOINTS.USER.LOGIN,
|
||||
);
|
||||
|
||||
const isRefreshTokenRequest = originalRequest?.url?.includes(
|
||||
API_ENDPOINTS.USER.REFRESH_TOKEN,
|
||||
);
|
||||
|
||||
// If we get a 401 on refresh token endpoint, logout immediately (token refresh failed)
|
||||
if (error.response?.status === 401 && isRefreshTokenRequest) {
|
||||
const logout = getLogoutCallback();
|
||||
if (logout) {
|
||||
logout();
|
||||
} else {
|
||||
// Fallback if logout callback is not registered
|
||||
Cookies.remove(COOKIE_KEYS.access_token);
|
||||
Cookies.remove(COOKIE_KEYS.refresh_token);
|
||||
Cookies.remove(COOKIE_KEYS.user);
|
||||
if (typeof window !== "undefined") {
|
||||
window.location.href = "/auth/sign-in";
|
||||
}
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
if (
|
||||
error.response?.status === 401 &&
|
||||
!originalRequest._retry &&
|
||||
@@ -55,7 +77,20 @@ export const responseErrorHandler = async (error: AxiosError) => {
|
||||
try {
|
||||
const refreshToken = Cookies.get(COOKIE_KEYS.refresh_token);
|
||||
if (!refreshToken) {
|
||||
throw new Error("Refresh token not found.");
|
||||
// No refresh token available, logout immediately
|
||||
const logout = getLogoutCallback();
|
||||
if (logout) {
|
||||
logout();
|
||||
} else {
|
||||
// Fallback if logout callback is not registered
|
||||
Cookies.remove(COOKIE_KEYS.access_token);
|
||||
Cookies.remove(COOKIE_KEYS.refresh_token);
|
||||
Cookies.remove(COOKIE_KEYS.user);
|
||||
if (typeof window !== "undefined") {
|
||||
window.location.href = "/auth/sign-in";
|
||||
}
|
||||
}
|
||||
return Promise.reject(new Error("Refresh token not found."));
|
||||
}
|
||||
|
||||
const response = await api.post(API_ENDPOINTS.USER.REFRESH_TOKEN, {
|
||||
@@ -81,14 +116,21 @@ export const responseErrorHandler = async (error: AxiosError) => {
|
||||
return api(originalRequest);
|
||||
} catch (refreshError) {
|
||||
processQueue(refreshError, null);
|
||||
Cookies.remove(COOKIE_KEYS.access_token);
|
||||
Cookies.remove(COOKIE_KEYS.refresh_token);
|
||||
Cookies.remove(COOKIE_KEYS.user);
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
localStorage.removeItem("auth-token");
|
||||
localStorage.removeItem("user-data");
|
||||
window.location.href = "/login";
|
||||
// Use logout callback from User context
|
||||
const logout = getLogoutCallback();
|
||||
if (logout) {
|
||||
logout();
|
||||
} else {
|
||||
// Fallback if logout callback is not registered
|
||||
Cookies.remove(COOKIE_KEYS.access_token);
|
||||
Cookies.remove(COOKIE_KEYS.refresh_token);
|
||||
Cookies.remove(COOKIE_KEYS.user);
|
||||
if (typeof window !== "undefined") {
|
||||
localStorage.removeItem("auth-token");
|
||||
localStorage.removeItem("user-data");
|
||||
window.location.href = "/auth/sign-in";
|
||||
}
|
||||
}
|
||||
|
||||
const errorMessage =
|
||||
|
||||
Reference in New Issue
Block a user