데브코스를 진행하던 와중... 1차 프로젝트를 하게 되었다.
시간은 그다지 길지 않은 2주 반안에 기획, 설계, 구현, 발표까지 완성해야 해서 여유롭게 코딩하지는 못하였다. 그래도 1차 프로젝트를 하면서 배운것도 많고, 해보고 싶은 것도 해보아서 이에 대한 회고를 작성하고자 한다.
프로젝트 진행에 관한 사항은 내가 담당했던 부분에서 회고하고자 한다.
사실 api는 데브코스 측에서 명세서를 주었다.
이 때문에 편한점도 있긴 있었으나(백엔드와의 추가적인 소통의 불필요) 이것이 단점으로 작용하기도 하였다. (추가적인 기능 구현시 제약)
로그인 api는 로그인시 token을 내려주는 형태였다.
이 token을 react-cookie를 사용하여 쿠키에 저장하였고 recoil의 atom에 저장하여 전역에서 접근 할 수 있게 끔 하였다. 전역 atom으로 왜 사용하였는가 하면 사실 api 호출시 token의 필요성 때문이였다. http method와 관련된 사항들은 api js 파일에 따로 함수를 만들어 사용하였는데 각기 필요한 컴포넌트 혹은 페이지에서 호출시 token을 이 함수로 전달 해줄 필요가 있었고, 필요 할때마다 쿠키에 접근해서 token을 가져오는 방식보다는 recoil로, 즉 전역상태로 관리하여 필요할때마다 이 state를 활용하는 방식이 더 효율적이라 판단하여 recoil로 token을 관리하여 주었다.
import { selector, atom } from "recoil";
import { getCookie, setCookie, removeCookie } from "../utils/cookie";
// jwtToken atom
export const jwtToken = atom({
key: "jwtToken",
default: getCookie("token"),
});
// 로그인 여부를 관리하는 atom
export const loginStatus = atom({
key: "LoginStatus",
default: false,
});
또한 login시 login 처리를 해줄 필요성이 있어서 로그인 프로세스에 사용하기 위해 selector를 사용하였다.
export const loginProcess = selector({
key: "loginProcess",
get: ({ get }) => {
return get(jwtToken);
},
set: ({ set }, newValue) => {
// 현재 시간에서 1일 후 만료
const TOKEN_EXPIRE_TIME = new Date(Date.now() + 60 * 60 * 24 * 1000);
// cookie 설정
setCookie("token", newValue, {
path: "/",
expires: TOKEN_EXPIRE_TIME,
});
// atom value update
set(jwtToken, newValue);
},
});
로그아웃 시에도 쿠키를 지우는 등의 로직이 필요하였기 때문에 selector를 사용하여주었다.
(여기서 { path: "/" }를 지정하지 않았더니 쿠키가 제대로 지워지지 않는 버그가 있었다.)
export const logoutProcess = selector({
key: "logoutProcess",
get: ({ get }) => {
return !get(loginStatus);
},
set: ({ set }) => {
removeCookie("token", { path: "/" });
set(jwtToken, "");
set(loginStatus, false);
},
});
또한 api중 token의 유효성을 검증해주는 api가 있어 token을 검증하는 selector 또한 만들어 주었다.(이 api의 response는 user 객체, 즉 userdata 이기 때문에 token의 유효성 검사와 함께 user data를 return 시켜 주었다.)
export const isUserAuthenticated = selector({
key: "isUserAuthenticated",
get: async ({ get }) => {
const token = get(jwtToken);
if (token) {
const res = await userAuth(token);
if (res && res.data) {
return {
isTokenValid: true,
userData: res.data,
};
}
}
return {
isTokenValid: false,
userData: null,
};
},
});
여기까지 봤다면, 의문점이 들 수 있다.
새로고침하거나 재접속하면? recoil에 저장된 state들이 초기화 되는 것 아닌가?
이 점을 해결하기 위한 방법으로 크게 두가지를 생각해 보았다.
1. recoil persist 사용하기
2. App.js에서 새로고침 & 재접속 시마다 인증 요청 보내기
여기서 나는 당연하게 2번을 선택하였다. 그 이유는 recoil persist는 localstorage에 값을 저장한다. 이때 localStorage에서 token 값이 노출이 되게 되고, token이 공격자에 의해서 쉽게 털릴 수 있기 때문이다.
useEffect(() => {
if (!isLogined && TokenExist) { //login은 안되어 있는데 token값이 있다면
if (isTokenValid) { //token이 valid한가?
setIsLogined(true); // valid 하다면 로그인 처리
setUserInfo(userData);
}
}
}, [isLogined, TokenExist, isTokenValid, userData]);
따라서 다음과 같이 코드를 App.js에 추가하여 새로고침 & 재접속 시마다 요청을 보내 확인 절차를 거치고 로그인 처리를 시켜 주었다.