ํ๋ก์ ํธ๋ฅผ ์งํํ๋ ์ค ํ๋กํ ์กฐํ์์ ๊ฐ์๊ธฐ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
์ค๋ฅ
๋คํธ์ํฌ ํญ์์ ํ์ธํ ์ค๋ฅ ์์ธ์ ํ ํฐ ๋ง๋ฃ ์ด์..!
ํ ํฐ ๋ง๋ฃ ์๊ฐ์ ํ์ธํด ๋ณด๋.. 1์๊ฐ์ธ ๊ฒ์ ํ์ธํ๋ค...
ํด๊ฒฐ ๊ณผ์ ...
์ฐ์ ๋น ๋ฅธ ํ ์คํธ๋ฅผ ์ํด ํ ํฐ์ 1๋ถ์ง๋ฆฌ ๋ฐ๊ธํ๋๋ก ํ๋ค.
export const login = async (userData) => {
return await authInstance.post("/login?expiresIn=1m", userData);
};
์ฒ์ ์๊ฐํ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์๋ต์ด status ๊ฐ 401 ์ธ ๊ฒฝ์ฐ ๋ก๊ทธ์์ ์ฒ๋ฆฌํ๊ณ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋๋ฆฌ๋ฉด ๋๊ฒ ์ง ํ๋๋ฐ, ๋ค๋ฅธ ์ค๋ฅ๋ ๋ค 401์ ์ฃผ๊ณ ์์๋ค.
๊ทธ๋ฌ๋ฉด ์๋ต์ผ๋ก "ํ ํฐ์ด ๋ง๋ฃ๋์์ต๋๋ค. ๋ค์ ๋ก๊ทธ์ธ ํด์ฃผ์ธ์." ๋ผ๋ ๋ฉ์ธ์ง๋ฅผ ์ฃผ๊ณ ์์ผ๋ ์ค๋ฅ ๋ฉ์ธ์ง๊ฐ ์ด์ ๊ฐ์ผ๋ฉด ์ฒ๋ฆฌํ๋๋ก ํ๋ ค๊ณ ํ๋๋ฐ tanstack query ๋ก ํธ์ถ ์ ์ค๋ฅ ๋ฐ์ ์ .. error ์ ์๋ฌ๋ฉ์ธ์ง๊ฐ ์์ด์ผ๋ ๊ฒ ๊ฐ์๋ฐ null ์ด๋ค..(?) ์ด๊ฑฐ๋ก๋ ํด๊ฒฐ์ ๋ชปํ๊ฒ ๊ตฐ.. ํจ์ค..
๋ค์ ๋ฐฉ๋ฒ์ ํด๋น ๋ฌธ์์ ๋์ ์๋ ๋๋ก axios ๋ก ํธ์ถ ํ catch ๋ก ์ก์์ ์ฒ๋ฆฌํด ๋ณผ ์ง ์๊ฐํ๋๋ฐ, ์ ๋ ๊ฒ ํ ๊ฒฝ์ฐ ๋ชจ๋ api ์์ฒญํ๋ ๊ณณ๋ง๋ค ์ฒ๋ฆฌ๋ฅผ ํด์ฃผ์ด์ผ ํด์ ์ค๋ณต์ฝ๋๊ฐ(ํ์ ์ ๋ณด ์กฐํ, ํ๋กํ ์์ ๋ฑ) ์๊ธด๋ค. ๊ทธ๋์ ์ด ๋ฐฉ๋ฒ๋ ์๋ํด๋ณด๋ค๊ฐ ํจ์ค..
/** ํ์์ ๋ณด ์กฐํ */
export const getUserProfile = async (token) => {
return await authInstance.get("/user", {
headers: {
Authorization: `Bearer ${token}`,
},
});
// ์ฌ๊ธฐ์ ์ฒ๋ฆฌํ ๊น ์๊ฐํ์
// .catch((error) => {
// console.log(error.response.data.message);
// });
};
/** ํ๋กํ ์์ */
export const updateProfile = async ({ nickname, token }) => {
... ์๋ต
};
๋ค์ ์๊ฐํ ๋ฐฉ์์ axios interceptor ์ด๋ค.
const authInstance = axios.create({
baseURL: "https://moneyfulpublicpolicy.co.kr",
});
authInstance.interceptors.response.use(
(response) => {
return response;
},
(error) => {
if (
error.response.data.message ===
"ํ ํฐ์ด ๋ง๋ฃ๋์์ต๋๋ค. ๋ค์ ๋ก๊ทธ์ธ ํด์ฃผ์ธ์."
) {
const { persist } = useUserStore();
const navigate = useNavigate();
persist.clearStorage();
navigate(HOME);
}
return Promise.reject(error);
}
);
์์ ๊ฐ์ด ์ธํฐ์ ํฐ์์ ํ๋ฉด ํ์ ์ ๋ณด ์กฐํ, ํ๋กํ ์กฐํํ ๋ ๊ณตํต์ผ๋ก ์ฒ๋ฆฌ๋ฅผ ํ ์ ์๋ค. ํ์ง๋ง ๋ฌธ์ ๊ฐ ์๊ฒผ๋๋ฐ.. useNavigate ๊ฐ์ ๋ฆฌ์กํธ ํ ์ ๋ฐ๋์ React ์ปดํฌ๋ํธ ํจ์ ๋ด์์ ํธ์ถ๋์ด์ผ ํ๋ค๋ ๊ฒ... ๋น๋๊ธฐ ํจ์ ํน์ React ์ปดํฌ๋ํธ ์ธ๋ถ์์๋ ์ฌ์ฉํ ์ ์๋ค๊ณ ํ๋ค.
๊ทธ๋ฌ๋ฉด.. ํ ์ ์ฌ์ฉํ์ง ์๊ณ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด๋ค๊ฐ..
์๋์ ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก ์๋ํ๋ค.
const authInstance = axios.create({
baseURL: "https://moneyfulpublicpolicy.co.kr",
});
authInstance.interceptors.response.use(
(response) => {
return response;
},
(error) => {
if (
error.response.data.message ===
"ํ ํฐ์ด ๋ง๋ฃ๋์์ต๋๋ค. ๋ค์ ๋ก๊ทธ์ธ ํด์ฃผ์ธ์."
) {
localStorage.removeItem("userInfo"); // ๋ก์ปฌ ์คํ ๋ฆฌ์ง์์ ๋ฐ์ดํฐ ์ญ์
alert("๋ก๊ทธ์ธ ๊ธฐ๊ฐ์ด ๋ง๋ฃ๋์์ต๋๋ค. ๋ค์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.");
window.location.href = HOME;
}
return Promise.reject(error);
}
);
์ด๊ฒ ์ต์ ... ์ธ์ค ์์๋๋ฐ ์ด ๋ฐฉ์์๋ ๋ฌธ์ ๊ฐ ์๋ค. ์ฌ์ค navigate ๋ ๋๊ณ window.location.href = HOME; ๋ฅผ ์ฌ์ฉํ ๊ฒ๋ ์ฐ์ฐํ์ง๋ง ๋ ํฐ ๋ฌธ์ ๋, ์ฌ์ฉํ๋ API ์ค json server ์ ์ฌ์ฉํ๋ test ๊ด๋ จ API ๋ ์์ฒญ ์ ํ ํฐ์ ์ฃผ์ง ์๋๋ค. ํ์ง๋ง ํ์ ๊ด๋ จ API ๋ ์์ฒญ ์ ํ ํฐ์ ํค๋์ ๋ฃ์ด์ ์์ฒญํ๊ธฐ ๋๋ฌธ์, ํ๋ฉด์์ ํญ ์ด๋ ์ ํ๋กํ ํ๋ฉด์์๋ ํ ํฐ ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ณ ํ ์คํธ ๊ด๋ จ ํ์ด์ง์์๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ง ์๋ ์ํฉ.
๊ทธ๋์ ๊ณ ๋ฏผ๋์ ํ๋กํ ๊ด๋ จ API ์์ ํ ํฐ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด ํ ์คํธ API ์๋ ์ํฅ์ด ๊ฐ๋๋ก ๋ณ๊ฒฝ์ ํ์๋ค.
๊ทธ ์์น๋ ProtectedRoute ์ด๋ค. ํ๋กํ๊ณผ ํ ์คํธ ํ์ด์ง๋ ๋ก๊ทธ์ธํ ์ ์ ๋ง ์ ๊ทผ์ด ๊ฐ๋ฅํ๊ธฐ๋๋ฌธ์ ์ด ๊ณณ์์ ๋ก๊ทธ์ธ ์ํ์ผ๋ฉด ๋ก๊ทธ์ธ ํ๋ฉด์ผ๋ก ๋ฆฌ๋ค์ด๋ ํธํด์ค๋ค. ํ๋กํ ํ์ด์ง์ ํ ์คํธํ์ด์ง๋ ์ด๊ณณ์ ๋ฌด์กฐ๊ฑด ๊ฑฐ์น๊ธฐ ๋๋ฌธ์ ์ด๊ณณ์์ ์ฒ๋ฆฌ๋ฅผ ํ๋ฉด ๋๋ค.
์ด๋ป๊ฒ? ๋ก๊ทธ์ธ ๋์ด์์ผ๋ฉด ์ ์ ์ ๋ณด ์์ ์๋ ํ ํฐ์ผ๋ก ํ์ ์กฐํ๋ฅผ ํ๊ณ ํ ํฐ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด ๋ก๊ทธ์์ & ๋ก๊ทธ์ธ ํ์ด์ง๋ก ์ด๋์ํค๊ธฐ
const ProtectedRouts = () => {
const { user, isLogin, setIsLogin, setUser, pageType } = useUserStore();
const navigate = useNavigate();
const clearUserIdStorage = useUserStore.persist.clearStorage;
/**
* ๋ก๊ทธ์ธ ํ์ ๊ฒฝ์ฐ, ์๋ก๊ณ ์นจํ๋ฉด ์ด ๋ก์ง์ด ์๋
* -> ํ์ ์ ๋ณด ์กฐํํด์ ํ ํฐ ์ ํจํ์ง ํ๋จ
* -> ์ค๋ฅ ๋ฐ์ํ๋ฉด ๋ก๊ทธ์์ ์ฒ๋ฆฌ ํ, ๋ก๊ทธ์ธ ํ์ด์ง๋ก ์ด๋
*/
useEffect(() => {
const getUser = async () => {
if (isLogin) {
try {
await getUserProfile(user.accessToken);
} catch (error) {
setUser(null);
setIsLogin(false);
clearUserIdStorage();
navigate(SING_IN);
}
}
};
getUser();
}, []);
/** ๋ก๊ทธ์ธ์ด ์๋ ์ํ์ด๋ฉด -> ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ณด๋ด๊ธฐ */
if (!isLogin) {
alert("๋ก๊ทธ์ธ ํ ์ด์ฉํด์ฃผ์ธ์.");
return <Navigate to={SING_IN} replace />;
}
return <Outlet />;
};
๊ทผ๋ฐ.... ์ด๊ฒ ์ญ์ ๋ฌธ์ ๊ฐ ์๋ค. useEffect ๋ก ์ธํด์ ํ์ด์ง ํ๋ฒ ์ด๋ํ๊ณ ๋์ ๋ค๋ฅธ ํ์ด์ง ์ด๋์ ๋์์ ์ ํ๋ค!! ใ ใ ๊ฐ ํ์ด์ง๋ง๋ค ๋ฆฌ๋๋๋ง์ ํด์ค ํธ๋ฆฌ๊ฑฐ๊ฐ ํ์ํ๋ค.
๊ทธ๋์ header ์์ ํ์ด์ง ์ด๋ํ ๋๋ง๋ค pageType ์ํ๋ฅผ ์ฃผ์ด์ ProtectedRouts ๊ฐ ๋ฆฌ๋๋๋ง์ด ๋์ด์ useEffect ๋ฅผ ๋ค์ ์คํํ๋๋ก ํ๋ค.
const Header = () => {
... ์๋ต
return (
<header>
... ์๋ต
{user ? (
<div className="flex gap-10">
<span className="text-black">{user.nickname}</span>
<button
onClick={() => {
setPageType("Profile");
navigate(PROFILE);
}}
>
ํ๋กํ
</button>
... ์๋ต
</div>
) : (
<Link to={SING_IN}>๋ก๊ทธ์ธ</Link>
)}
</div>
</header>
);
};
export default Header;
ํ์ด์ง ์ด๋ ํ ๋๋ง๋ค setPageType("Profile") ์ ๊ฐ์ด pageType ๋ฅผ ์ค์ ํด์ ๋ฆฌ๋๋๋ง์ด ์ผ์ด๋๋๋ก ํ๋ค...
const ProtectedRouts = () => {
const { user, isLogin, setIsLogin, setUser, pageType } = useUserStore();
const navigate = useNavigate();
const clearUserIdStorage = useUserStore.persist.clearStorage;
useEffect(() => {
const getUser = async () => {
if (isLogin) {
try {
await getUserProfile(user.accessToken);
} catch (error) {
...์๋ต
}
}
};
getUser();
}, [pageType]);
... ์๋ต
return <Outlet />;
};
useEffect ์๋ pageType ์์กดํ๋๋ก ํ๋ค.
์ผ๋จ.. ์ํ๋ ๋์๋๋ก ํด๊ฒฐ์ํ๋ค.. ํ์ง๋ง ์ด๊ฒ์ ๊ฒ ์๋ํ๊ณ ์ฝ๋ ์์ ํ๊ณ ํ๋๊น ๊ดํ ๊ผฌ์ธ ๋๋์ด๊ณ ์ฐ์ฐํ ๊ธฐ๋ถ์ด ๋ ๋ค. ์ฒ์๋ถํฐ ์ค๊ณ๋ฅผ ์ํ๋ค๋ฉด.. ์ข์์ ํ ๋ฐ ์์ฌ์์ด ์๋ค.
์ด ์ด์๋ฅผ ์๋ฒฝํ๊ฒ ํด๊ฒฐํ๋ค..? โ ์๋๋ค. ํ์ง๋ง ๋ด๊ฐ ์ฒ์ ์ค๊ณํ ๊ตฌ์กฐ์์ ์ต๋ํ ํด๊ฒฐํด ๋ณผ ์ ์๋ ๋ฐฉ๋ฒ์ผ๋ก ์ด๋ ์ ๋ ํด๊ฒฐํ๋ค๊ณ ๋ณธ๋ค.
์ฒ์์ผ๋ก ๋ก๊ทธ์ธ, ํ์๊ฐ์ , ๊ถํ ์ฒ๋ฆฌ, ํ ํฐ ์ฒ๋ฆฌ, ์๋ก๊ณ ์นจ ์ ๋ถ ๋ค ํ๋ฉด์ ๋๋ ๊ฒ์.. ํ๋ก ํธ๋จ์์ ๊ณ ๋ คํด์ผ ํ ์ํฉ์ด ๊ต์ฅ์ด ๋ง๋ค๋ ๊ฒ์ ๋๊ผ๋ค..
์ด๋ ต๊ณ ๋จธ๋ฆฌ ํฐ์ง๋ ํ๋ก์ ํธ์๋ค.. ๋์ค์ ๋ค์ ์ด ํ๋ก์ ํธ๋ฅผ ๋ดค์ ๋๋ ์ด๋์๋ถํฐ ์๋ชป๋๋์ง ๊นจ๋ซ๋ ์๊ฐ์ด ์ค๋ฉด ์ข๊ฒ ๋ค.