When the browser refreshes, React state resets, because the app reloads.
To persist authentication, the backend must provide a reliable way to re-validate the user and the frontend must store tokens safely.
Below is the proper way to do it.
✅ 1. Store tokens securely (not in React state)
React state always disappears on refresh.
You must store tokens in:
Option A — HttpOnly Cookies (Recommended, Secure)
- Backend sets token in secure cookie
- Browser sends cookie automatically on refresh
- JavaScript cannot read it → protects from XSS
Option B — LocalStorage (Common but less secure)
- Token stored manually
- Must extract and re-verify on refresh
- Vulnerable to XSS
✅ 2. On page refresh, call a backend “/me” or “/refresh” endpoint
When the app loads, run a check:
GET /auth/me → with JWT
or
POST /auth/refresh → with refresh token
This endpoint returns user details + new access token.
Example /auth/me logic:
router.get("/me", authMiddleware, (req, res) => {
res.json({ user: req.user });
});
Frontend calls this on startup and restores auth state.
✅ 3. Use Access Token + Refresh Token pattern
This is the most reliable production solution.
Access Token
- Short-lived (5–15 minutes)
- Stored in memory or HttpOnly cookie
Refresh Token
- Long-lived (7–30 days)
- Stored only in HttpOnly cookie
- Used to get new access token when page refreshes
Flow:
- User logs in → backend sets refresh token in HttpOnly cookie
- On reload:
- React loads
- React calls
/auth/refresh - Backend verifies refresh token
- Backend sends new access token
- React sets the user in global state again
This way, authentication never breaks on refresh.
✅ 4. Frontend Implementation Example (React)
App.js
useEffect(() => {
const checkAuth = async () => {
try {
const res = await axios.get("/auth/me", { withCredentials: true });
setUser(res.data.user);
} catch (err) {
setUser(null);
}
};
checkAuth();
}, []);
✔ Automatically restores user session on reload
✔ No need to store React state manually
✅ 5. Backend Implementation (Express)
Middleware: Verify JWT
const auth = (req, res, next) => {
const token = req.cookies.accessToken;
if (!token) return res.status(401).json({ message: "Unauthorized" });
try {
const user = jwt.verify(token, process.env.JWT_SECRET);
req.user = user;
next();
} catch {
res.status(403).json({ message: "Invalid token" });
}
};
Refresh route:
router.post("/refresh", (req, res) => {
const token = req.cookies.refreshToken;
if (!token) return res.sendStatus(403);
const user = jwt.verify(token, process.env.REFRESH_SECRET);
const newAccess = jwt.sign({ id: user.id }, process.env.JWT_SECRET, { expiresIn: "15m" });
res.cookie("accessToken", newAccess, { httpOnly: true, secure: true });
res.json({ success: true });
});
🎯 Final Interview Answer (Short Summary)
To persist authentication in a MERN app after page refresh, store tokens safely (preferably in HttpOnly cookies), and on app load call a backend endpoint like
/auth/meor/auth/refreshto restore the user's session.
Using Access Token + Refresh Token is the industry standard and ensures authentication remains valid even after a browser refresh.