From 78baa4cc3123a3c50ae425a933960c88485b5b3e Mon Sep 17 00:00:00 2001 From: Saurabh Kumar Bajpai Date: Wed, 20 May 2026 02:15:57 +0530 Subject: [PATCH] fix: preserve session on transient auth checks --- frontend/src/context/AuthContext.jsx | 35 ++++++++++++++++++++++------ frontend/src/services/api.js | 5 ++-- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/frontend/src/context/AuthContext.jsx b/frontend/src/context/AuthContext.jsx index dcdc4ed..2607e25 100644 --- a/frontend/src/context/AuthContext.jsx +++ b/frontend/src/context/AuthContext.jsx @@ -1,3 +1,4 @@ +/* eslint-disable react-refresh/only-export-components */ import { createContext, useContext, useState, useEffect } from "react"; import { getProfile } from "../services/userService"; @@ -10,9 +11,20 @@ export const useAuth = () => { }; export const AuthProvider = ({ children }) => { - const [user, setUser] = useState(null); + const readCachedUser = () => { + try { + const cachedUser = localStorage.getItem("user"); + return cachedUser ? JSON.parse(cachedUser) : null; + } catch { + localStorage.removeItem("user"); + return null; + } + }; + + const [user, setUser] = useState(readCachedUser); const [token, setToken] = useState(localStorage.getItem("token")); const [loading, setLoading] = useState(true); + const [authError, setAuthError] = useState(""); const isAuthenticated = !!token && !!user; @@ -24,12 +36,19 @@ export const AuthProvider = ({ children }) => { setToken(storedToken); const response = await getProfile(); setUser(response.data); + localStorage.setItem("user", JSON.stringify(response.data)); + setAuthError(""); } catch (error) { - // Token expired or invalid - localStorage.removeItem("token"); - localStorage.removeItem("user"); - setToken(null); - setUser(null); + if ([401, 403].includes(error?.response?.status)) { + localStorage.removeItem("token"); + localStorage.removeItem("user"); + setToken(null); + setUser(null); + setAuthError(""); + } else { + setUser((currentUser) => currentUser || readCachedUser()); + setAuthError("We could not refresh your profile. Your session is preserved and will retry when the connection is stable."); + } } } setLoading(false); @@ -39,8 +58,10 @@ export const AuthProvider = ({ children }) => { const login = (newToken, userData) => { localStorage.setItem("token", newToken); + localStorage.setItem("user", JSON.stringify(userData)); setToken(newToken); setUser(userData); + setAuthError(""); }; const logout = () => { @@ -50,7 +71,7 @@ export const AuthProvider = ({ children }) => { setUser(null); }; - const value = { user, setUser, token, isAuthenticated, loading, login, logout }; + const value = { user, setUser, token, isAuthenticated, loading, authError, login, logout }; return {children}; }; diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js index 4eb4d6f..62d1b06 100644 --- a/frontend/src/services/api.js +++ b/frontend/src/services/api.js @@ -4,6 +4,7 @@ const api = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, headers: { "Content-Type": "application/json" }, }); +const AUTH_FAILURE_STATUSES = new Set([401, 403]); // Request interceptor - inject auth token api.interceptors.request.use((config) => { @@ -14,11 +15,11 @@ api.interceptors.request.use((config) => { return config; }); -// Response interceptor - handle 401 +// Response interceptor - clear stored sessions only for real auth failures api.interceptors.response.use( (response) => response, (error) => { - if (error.response?.status === 401) { + if (AUTH_FAILURE_STATUSES.has(error.response?.status)) { localStorage.removeItem("token"); localStorage.removeItem("user"); window.location.href = "/login";