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);
|
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 {
|
interface UserProviderProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
@@ -47,6 +58,14 @@ export const UserProvider: React.FC<UserProviderProps> = ({ children }) => {
|
|||||||
Cookies.remove(COOKIE_KEYS.user);
|
Cookies.remove(COOKIE_KEYS.user);
|
||||||
window.location.href = "/auth/sign-in";
|
window.location.href = "/auth/sign-in";
|
||||||
}, [router]);
|
}, [router]);
|
||||||
|
|
||||||
|
// Register logout callback for use in API interceptors
|
||||||
|
useEffect(() => {
|
||||||
|
registerLogoutCallback(logout);
|
||||||
|
return () => {
|
||||||
|
globalLogoutCallback = null;
|
||||||
|
};
|
||||||
|
}, [logout]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try {
|
try {
|
||||||
const tokenFromCookie = Cookies.get(COOKIE_KEYS.access_token);
|
const tokenFromCookie = Cookies.get(COOKIE_KEYS.access_token);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { AxiosError, AxiosResponse } from "axios";
|
import { AxiosError, AxiosResponse } from "axios";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
|
import { getLogoutCallback } from "../contexts/User.ctx";
|
||||||
import { API_ENDPOINTS, api } from "../lib/api";
|
import { API_ENDPOINTS, api } from "../lib/api";
|
||||||
import { COOKIE_KEYS } from "../lib/shared/cookies";
|
import { COOKIE_KEYS } from "../lib/shared/cookies";
|
||||||
|
|
||||||
@@ -31,6 +32,27 @@ export const responseErrorHandler = async (error: AxiosError) => {
|
|||||||
API_ENDPOINTS.USER.LOGIN,
|
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 (
|
if (
|
||||||
error.response?.status === 401 &&
|
error.response?.status === 401 &&
|
||||||
!originalRequest._retry &&
|
!originalRequest._retry &&
|
||||||
@@ -55,7 +77,20 @@ export const responseErrorHandler = async (error: AxiosError) => {
|
|||||||
try {
|
try {
|
||||||
const refreshToken = Cookies.get(COOKIE_KEYS.refresh_token);
|
const refreshToken = Cookies.get(COOKIE_KEYS.refresh_token);
|
||||||
if (!refreshToken) {
|
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, {
|
const response = await api.post(API_ENDPOINTS.USER.REFRESH_TOKEN, {
|
||||||
@@ -81,14 +116,21 @@ export const responseErrorHandler = async (error: AxiosError) => {
|
|||||||
return api(originalRequest);
|
return api(originalRequest);
|
||||||
} catch (refreshError) {
|
} catch (refreshError) {
|
||||||
processQueue(refreshError, null);
|
processQueue(refreshError, null);
|
||||||
Cookies.remove(COOKIE_KEYS.access_token);
|
|
||||||
Cookies.remove(COOKIE_KEYS.refresh_token);
|
|
||||||
Cookies.remove(COOKIE_KEYS.user);
|
|
||||||
|
|
||||||
if (typeof window !== "undefined") {
|
// Use logout callback from User context
|
||||||
localStorage.removeItem("auth-token");
|
const logout = getLogoutCallback();
|
||||||
localStorage.removeItem("user-data");
|
if (logout) {
|
||||||
window.location.href = "/login";
|
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 =
|
const errorMessage =
|
||||||
|
|||||||
Reference in New Issue
Block a user