아래 글로 리뉴얼 되었습니다.
https://securityinit.tistory.com/248
React 에서 로그인을 관리하는 방법
이번 프로젝트에서 프론트엔드로 리액트를 선택했다.
리액트는 처음이었기에 많은 어려움을 겪었다.
오늘은 그 중 로그인 관리를 어떻게 하고 있는지 적어보려 한다.
인증을 jwt 토큰을 활용하기로 했고, 로그인에 성공하면 백엔드에서 accessToken을 보내준다.
프론트엔드에서 로그인을 유지하기 위한 방법을 생각해봤다.
백엔드에서 accessToken을 받았다면 로그인에 성공한 것으로 간주해, “로그인 성공 상태”를 저장한다.받은 AccessToken을 local stroage에 저장한다.추후 로그인이 필요한 요청을 보낼 땐 local Storage에 저장되어 있는 accessToken을 활용해 요청을 보낸다.
1. 로그인된 상태를 유지한다.
[isLoggedIn, setIsLoggedIn] = useState(false);
로그인 성공시
setIsLoggedIn(true)
먼저 위와 같은 방식을 사용하려 했지만, 한 컴포넌트 내의 state는 다른 컴포넌트에서 유지되지 않았다.
새로고침을 하거나, 페이지를 이동하면 계속해서 로그인이 풀리는 현상이 있었다
이를 해결하기 위해 여러 방법을 찾아보니, 상태관리를 위한 라이브러리를 사용하는 것이 좋다는 것을 알게되었고,
Redux와 zustand 중 고민하다 zustand 라이브러리를 사용하기로 결정했다.
(Redux의 경우, 문법이 너무 길고, 어려웠으며 러닝커브가 너무 높을 것이란 생각이 들어 훨씬 간단하다고 알려진 zustand를 사용하기로 결정했다.)
먼저 zustand 스토어를 만든다.
// src/store/useAuthStore.tsx
import { create } from "zustand";
import { persist } from "zustand/middleware";
interface AuthStore {
isLoggedIn: boolean;
login: () => void;
logout: () => void;
kakaoLogin: () => void;
}
const useAuthStore = create(
persist<AuthStore>(
(set) => ({
isLoggedIn: false,
login: () => {
const userLocalStorage = localStorage.getItem("accessToken");
if (userLocalStorage) {
set({ isLoggedIn: true });
}
},
kakaoLogin: () => {
const userLocalStorage = localStorage.getItem("accessToken");
if (userLocalStorage) {
set({ isLoggedIn: true });
}
},
logout: () => {
set({ isLoggedIn: false });
localStorage.clear();
},
}),
{
name: "userLoginStatus",
}
)
);
export default useAuthStore;
로그인은 default로 false로 설정했다.
그 후 로그인에 성공한다면 login() 을 실행시켜 isLoggedIn을 true로 변경시켜 줬다.
import axios from "axios";
import useAuthStore from "store/useAuthStore";
const LogIn = () => {
...
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
try {
const res = await axios.post(
`http://localhost:${
process.env.REACT_APP_SERVER_PORT || 3000
}/api/auth/sign-in`,
{
email,
password,
},
{
withCredentials: true,
}
);
localStorage.setItem("accessToken", res.data.data.accessToken);
localStorage.setItem("refreshToken", res.data.data.refreshToken);
login(); // <- 이 부분!
navigate("/home", { replace: true });
} catch (err: any) {
console.log(err);
// alert(err.response?.data?.message);
setErrorMessage(err.response?.data?.message || "로그인에 실패했습니다.");
setInputs({
email: "",
password: "",
});
}
};
...
return (
<Container>
...
</Container>
);
};
export default LogIn;
위와 같이 로그인에 성공한 경우 , isLoggedIn을 true로 변경시켜줬다.
로그인 유지를 위해
import { Routes, Route, Navigate, useNavigate } from "react-router-dom";
interface ProtectedRouteProps {
children: ReactNode;
}
const App = () => {
const { isLoggedIn } = useAuthStore();
const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ children }) => {
const navigate = useNavigate();
useEffect(() => {
if (isLoggedIn) {
navigate("/home");
}
}, [isLoggedIn, navigate]);
return <>{children}</>;
};
return (
<Routes>
{/* 로그인 안해도 접근 가능한 url */}
<Route
path="/login"
element={
<ProtectedRoute>
<LogIn />
</ProtectedRoute>
}
/>
<Route
path="/signup"
element={
<ProtectedRoute>
<SignUp />
</ProtectedRoute>
}
/>
{isLoggedIn ? (
<>
{/* 로그인 해야 접근 가능한 url */}
</>
) : (
<Route path="/*" element={<Navigate replace to="/login" />} />
)}
</Routes>
);
};
export default App;
라우팅 역시 isLoggedIn의 값에 따라 접근 권한을 나누었다.
이렇게 하니 새로 고침을 해도 로그인이 풀리지 않았다.